10825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/*
20825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Copyright (C) 2010 The Android Open Source Project
30825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
40825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Licensed under the Apache License, Version 2.0 (the "License");
50825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * you may not use this file except in compliance with the License.
60825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * You may obtain a copy of the License at
70825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
80825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *      http://www.apache.org/licenses/LICENSE-2.0
90825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville *
100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * Unless required by applicable law or agreed to in writing, software
110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * distributed under the License is distributed on an "AS IS" BASIS,
120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * See the License for the specific language governing permissions and
140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * limitations under the License.
150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
170825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepackage com.android.internal.telephony.sip;
180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
190825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.content.Context;
200825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.media.AudioManager;
210825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.rtp.AudioGroup;
220825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipAudioCall;
230825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipErrorCode;
240825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipException;
250825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipManager;
260825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipProfile;
270825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.net.sip.SipSession;
280825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.AsyncResult;
290825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.os.Message;
30b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensenimport android.telephony.DisconnectCause;
310825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.PhoneNumberUtils;
320825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.telephony.ServiceState;
330825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport android.text.TextUtils;
3499c2e1d6749cfad2a8ca94a47857d8c3bfc09454Wink Savilleimport android.telephony.Rlog;
350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
360825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Call;
370825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.CallStateException;
380825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Connection;
390825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.Phone;
400825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneConstants;
410825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport com.android.internal.telephony.PhoneNotifier;
420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
430825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.text.ParseException;
440825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.List;
450825495a331bb44df395a0cdb79fab85e68db5d5Wink Savilleimport java.util.regex.Pattern;
460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville/**
480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville * {@hide}
490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville */
500825495a331bb44df395a0cdb79fab85e68db5d5Wink Savillepublic class SipPhone extends SipPhoneBase {
510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final String LOG_TAG = "SipPhone";
52f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static final boolean DBG = true;
53ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville    private static final boolean VDBG = false; // STOPSHIP if true
540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
5799a3e79b952978f781c1178010129231701e231bBrad Ebinger    // Minimum time needed between hold/unhold requests.
5899a3e79b952978f781c1178010129231701e231bBrad Ebinger    private static final long TIMEOUT_HOLD_PROCESSING = 1000; // ms
590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    // A call that is ringing or (call) waiting
6122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mRingingCall = new SipCall();
6222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mForegroundCall = new SipCall();
6322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville    private SipCall mBackgroundCall = new SipCall();
640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipManager mSipManager;
660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private SipProfile mProfile;
670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6899a3e79b952978f781c1178010129231701e231bBrad Ebinger    private long mTimeOfLastValidHoldRequest = System.currentTimeMillis();
6999a3e79b952978f781c1178010129231701e231bBrad Ebinger
700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
71ff4e317d24f0d23bdc0f306d53ddc51f2f1ecf6aWink Saville        super("SIP:" + profile.getUriString(), context, notifier);
720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
73b4350195add6b77d9352a41e1a8e5580a6a4816cSantos Cordon        if (DBG) log("new SipPhone: " + hidePii(profile.getUriString()));
7422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mRingingCall = new SipCall();
7522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall = new SipCall();
7622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mBackgroundCall = new SipCall();
770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mProfile = profile;
780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        mSipManager = SipManager.newInstance(context);
790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(Object o) {
830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (o == this) return true;
840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!(o instanceof SipPhone)) return false;
850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        SipPhone that = (SipPhone) o;
860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString().equals(that.mProfile.getUriString());
870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public String getSipUri() {
900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return mProfile.getUriString();
910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean equals(SipPhone phone) {
940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return getSipUri().equals(phone.getSipUri());
950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
97e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal    public Connection takeIncomingCall(Object incomingCall) {
98f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // FIXME: Is synchronizing on the class necessary, should we use a mLockObj?
99f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        // Also there are many things not synchronized, of course
10058dd6858dc8013b680ea003d22063fd65ed5fe1cAmit Mahajan        // this may be true of GsmCdmaPhone too!!!
1010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
102f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (!(incomingCall instanceof SipAudioCall)) {
103e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: ret=null, not a SipAudioCall");
104e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
105f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isAlive()) {
107e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: ret=null, ringingCall not alive");
108e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
109f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
1100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // FIXME: is it true that we cannot take any incoming call if
1120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // both foreground and background are active
11322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()
11422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    && mBackgroundCall.getState().isAlive()) {
115f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
116e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    log("takeIncomingCall: ret=null," + " foreground and background both alive");
117f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
118e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                return null;
1190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
1220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
123e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("takeIncomingCall: taking call from: "
124f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                        + hidePii(sipAudioCall.getPeerProfile().getUriString()));
1250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String localUri = sipAudioCall.getLocalProfile().getUriString();
1260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (localUri.equals(mProfile.getUriString())) {
12722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    boolean makeCallWait = mForegroundCall.getState().isAlive();
128e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    SipConnection connection = mRingingCall.initIncomingCall(sipAudioCall,
129e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                            makeCallWait);
130e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    if (sipAudioCall.getState() != SipSession.State.INCOMING_CALL) {
1310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        // Peer cancelled the call!
132e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                        if (DBG) log("    takeIncomingCall: call cancelled !!");
13322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        mRingingCall.reset();
134e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                        connection = null;
1350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
136e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                    return connection;
1370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
1380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (Exception e) {
1390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Peer may cancel the call at any time during the time we hook
1400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // up ringingCall with sipAudioCall. Clean up ringingCall when
1410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // that happens.
142e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal                if (DBG) log("    takeIncomingCall: exception e=" + e);
14322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.reset();
1440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
145e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            if (DBG) log("takeIncomingCall: NOT taking !!");
146e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            return null;
1470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
150f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1516d05f561549a66b597a5119665ccc3bf8a962d16Andrew Lee    public void acceptCall(int videoState) throws CallStateException {
1520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
15322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mRingingCall.getState() == Call.State.INCOMING) ||
15422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    (mRingingCall.getState() == Call.State.WAITING)) {
155f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("acceptCall: accepting");
1560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // Always unmute when answering a new call
15722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.setMute(false);
15822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.acceptCall();
1590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
160f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
161f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("acceptCall:" +
162f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
163f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
169f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void rejectCall() throws CallStateException {
1710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
17222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mRingingCall.getState().isRinging()) {
173f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) log("rejectCall: rejecting");
17422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mRingingCall.rejectCall();
1750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
176f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (DBG) {
177f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    log("rejectCall:" +
178f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                        " throw CallStateException(\"phone not ringing\")");
179f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                }
1800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("phone not ringing");
1810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
1820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
185f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
1866bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn    public Connection dial(String dialString, int videoState) throws CallStateException {
1870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
1886bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn            return dialInternal(dialString, videoState);
1890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1926bbcbfd62c9aa5787e7c33936e2246ff05b59d58Tyler Gunn    private Connection dialInternal(String dialString, int videoState)
1930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throws CallStateException {
194b4350195add6b77d9352a41e1a8e5580a6a4816cSantos Cordon        if (DBG) log("dialInternal: dialString=" + hidePii(dialString));
1950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        clearDisconnected();
1960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!canDial()) {
198f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            throw new CallStateException("dialInternal: cannot dial in current state");
1990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
20022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() == SipCall.State.ACTIVE) {
2010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switchHoldingAndActive();
2020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
20322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        if (mForegroundCall.getState() != SipCall.State.IDLE) {
2040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            //we should have failed in !canDial() above before we get here
2050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("cannot dial in current state");
2060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
20822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        mForegroundCall.setMute(false);
2090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        try {
21022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection c = mForegroundCall.dial(dialString);
2110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return c;
2120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } catch (SipException e) {
213f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("dialInternal: ", e);
2140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            throw new CallStateException("dial error: " + e);
2150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
218f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void switchHoldingAndActive() throws CallStateException {
22099a3e79b952978f781c1178010129231701e231bBrad Ebinger        // Wait for at least TIMEOUT_HOLD_PROCESSING ms to occur before sending hold/unhold requests
22199a3e79b952978f781c1178010129231701e231bBrad Ebinger        // to prevent spamming the SipAudioCall state machine and putting it into an invalid state.
22299a3e79b952978f781c1178010129231701e231bBrad Ebinger        if (!isHoldTimeoutExpired()) {
22399a3e79b952978f781c1178010129231701e231bBrad Ebinger            if (DBG) log("switchHoldingAndActive: Disregarded! Under " + TIMEOUT_HOLD_PROCESSING +
22499a3e79b952978f781c1178010129231701e231bBrad Ebinger                    " ms...");
22599a3e79b952978f781c1178010129231701e231bBrad Ebinger            return;
22699a3e79b952978f781c1178010129231701e231bBrad Ebinger        }
227df0280231c51a24a0b66c24034827d7f73d6e1acSantos Cordon        if (DBG) log("switchHoldingAndActive: switch fg and bg");
2280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
22922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.switchWith(mBackgroundCall);
23022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold();
23122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold();
2320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
235f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canConference() {
237f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (DBG) log("canConference: ret=true");
2380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return true;
2390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
241f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference() throws CallStateException {
2430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
24422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if ((mForegroundCall.getState() != SipCall.State.ACTIVE)
24522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    || (mForegroundCall.getState() != SipCall.State.ACTIVE)) {
2460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("wrong state to merge calls: fg="
24722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mForegroundCall.getState() + ", bg="
24822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mBackgroundCall.getState());
2490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
250f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (DBG) log("conference: merge fg & bg");
25122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge(mBackgroundCall);
2520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void conference(Call that) throws CallStateException {
2560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
2570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!(that instanceof SipCall)) {
2580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("expect " + SipCall.class
2590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + ", cannot merge with " + that.getClass());
2600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
26122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.merge((SipCall) that);
2620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
265f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean canTransfer() {
2670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return false;
2680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
270f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
271cbaa45bbf2cab852b6c9c3a887e9f803d4e857eaWink Saville    public void explicitCallTransfer() {
2720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        //mCT.explicitCallTransfer();
2730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
275f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void clearDisconnected() {
2770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
27822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mRingingCall.clearDisconnected();
27922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.clearDisconnected();
28022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mBackgroundCall.clearDisconnected();
2810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
2820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            updatePhoneState();
2830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyPreciseCallStateChanged();
2840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
287f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendDtmf(char c) {
2890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
290f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("sendDtmf called with invalid character '" + c + "'");
29122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        } else if (mForegroundCall.getState().isAlive()) {
2920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
29322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mForegroundCall.sendDtmf(c);
2940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
2950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
2960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
2970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
298f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
2990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void startDtmf(char c) {
3000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (!PhoneNumberUtils.is12Key(c)) {
301f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            loge("startDtmf called with invalid character '" + c + "'");
3020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
3030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sendDtmf(c);
3040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
307f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void stopDtmf() {
3090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // no op
3100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void sendBurstDtmf(String dtmfString) {
313f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("sendBurstDtmf() is a CDMA method");
3140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
316f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getOutgoingCallerIdDisplay(Message onComplete) {
3180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
323f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
3250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           Message onComplete) {
3260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what's this for SIP?
3270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
331f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void getCallWaiting(Message onComplete) {
3330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
3340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AsyncResult.forMessage(onComplete, null, null);
3350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        onComplete.sendToTarget();
3360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
338f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setCallWaiting(boolean enable, Message onComplete) {
3400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: what to reply?
341f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        loge("call waiting not supported");
3420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    @Override
345184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota    public void setEchoSuppressionEnabled() {
346184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota        // Echo suppression may not be available on every device. So, check
347184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota        // whether it is supported
3480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
349184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
350184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            String echoSuppression = audioManager.getParameters("ec_supported");
351184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            if (echoSuppression.contains("off")) {
352184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota                mForegroundCall.setAudioGroupMode();
353184ffb58812108624ea0be2732cdae511b99d09dVidyakumar Athota            }
3540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
357f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public void setMute(boolean muted) {
3590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        synchronized (SipPhone.class) {
36022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mForegroundCall.setMute(muted);
3610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
3620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
364f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public boolean getMute() {
36622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return (mForegroundCall.getState().isAlive()
36722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                ? mForegroundCall.getMute()
36822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                : mBackgroundCall.getMute());
3690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
371f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getForegroundCall() {
37322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mForegroundCall;
3740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
376f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getBackgroundCall() {
37822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mBackgroundCall;
3790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
381f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public Call getRingingCall() {
38322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville        return mRingingCall;
3840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
386f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    @Override
3870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    public ServiceState getServiceState() {
3880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // FIXME: we may need to provide this when data connectivity is lost
3890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // or when server is down
3900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return super.getServiceState();
3910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getUriString(SipProfile p) {
3940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // SipProfile.getUriString() may contain "SIP:" and port
3950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        return p.getUserName() + "@" + getSipDomain(p);
3960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
3970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
3980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private String getSipDomain(SipProfile p) {
3990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        String domain = p.getSipDomain();
4000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        // TODO: move this to SipProfile
4010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        if (domain.endsWith(":5060")) {
4020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain.substring(0, domain.length() - 5);
4030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        } else {
4040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return domain;
4050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
4070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
408f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
409f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
410f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        int sessionState = sipAudioCall.getState();
411f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        switch (sessionState) {
412f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
413f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL:
414f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
415f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
416f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
417f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
418f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
419f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            default:
420f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                slog("illegal connection state: " + sessionState);
421f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return Call.State.DISCONNECTED;
422f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
423f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
424f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
42599a3e79b952978f781c1178010129231701e231bBrad Ebinger    private synchronized boolean isHoldTimeoutExpired() {
42699a3e79b952978f781c1178010129231701e231bBrad Ebinger        long currTime = System.currentTimeMillis();
42799a3e79b952978f781c1178010129231701e231bBrad Ebinger        if ((currTime - mTimeOfLastValidHoldRequest) > TIMEOUT_HOLD_PROCESSING) {
42899a3e79b952978f781c1178010129231701e231bBrad Ebinger            mTimeOfLastValidHoldRequest = currTime;
42999a3e79b952978f781c1178010129231701e231bBrad Ebinger            return true;
43099a3e79b952978f781c1178010129231701e231bBrad Ebinger        }
43199a3e79b952978f781c1178010129231701e231bBrad Ebinger        return false;
43299a3e79b952978f781c1178010129231701e231bBrad Ebinger    }
43399a3e79b952978f781c1178010129231701e231bBrad Ebinger
434f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void log(String s) {
435f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
436f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
437f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
438f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private static void slog(String s) {
439f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.d(LOG_TAG, s);
440f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
441f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
442f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s) {
443f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s);
444f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
445f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
446f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    private void loge(String s, Exception e) {
447f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        Rlog.e(LOG_TAG, s, e);
448f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville    }
449f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipCall extends SipCallBase {
451f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SC_TAG = "SipCall";
452f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_DBG = true;
453f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SC_VDBG = false; // STOPSHIP if true
454f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
4550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void reset() {
456f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("reset");
45722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.clear();
4580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.IDLE);
4590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void switchWith(SipCall that) {
462f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("switchWith");
4630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall tmp = new SipCall();
4650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                tmp.takeOver(this);
4660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                this.takeOver(that);
4670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                that.takeOver(tmp);
4680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void takeOver(SipCall that) {
472f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("takeOver");
47322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections = that.mConnections;
47422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mState = that.mState;
47522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
4760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).changeOwner(this);
4770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Phone getPhone() {
4820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return SipPhone.this;
4830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
4860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public List<Connection> getConnections() {
487f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("getConnections");
4880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
4890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // FIXME should return Collections.unmodifiableList();
49022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                return mConnections;
4910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
4920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
4930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
4940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        Connection dial(String originalNumber) throws SipException {
495f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx"));
496f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            // TODO: Should this be synchronized?
4970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String calleeSipUri = originalNumber;
4980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (!calleeSipUri.contains("@")) {
4990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
5000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
5010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        calleeSipUri + "@");
5020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
5040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipProfile callee =
5050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        new SipProfile.Builder(calleeSipUri).build();
5060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection c = new SipConnection(this, callee,
5070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        originalNumber);
5080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                c.dial();
50922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mConnections.add(c);
5100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(Call.State.DIALING);
5110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                return c;
5120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (ParseException e) {
5130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new SipException("dial", e);
5140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
5180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
5190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
52022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (mState.isAlive()) {
521f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: call " + getState()
5220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(State.DISCONNECTING);
5240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    CallStateException excp = null;
52522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    for (Connection c : mConnections) {
5260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        try {
5270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            c.hangup();
5280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        } catch (CallStateException e) {
5290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            excp = e;
5300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
5310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
5320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (excp != null) throw excp;
5330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } else {
534f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("hangup: dead call " + getState()
5350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + ": " + this + " on phone " + getPhone());
5360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
5370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
5380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
540e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal        SipConnection initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
5410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipProfile callee = sipAudioCall.getPeerProfile();
5420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipConnection c = new SipConnection(this, callee);
54322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(c);
5440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
5460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            c.initIncomingCall(sipAudioCall, newState);
5470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
5490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyNewRingingConnectionP(c);
550e86a8d443e921caec2c398ef74f0b6d573a15bb1Sailesh Nepal            return c;
5510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void rejectCall() throws CallStateException {
554f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("rejectCall:");
5550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            hangup();
5560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
559f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("acceptCall: accepting");
56022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (this != mRingingCall) {
5610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a non-ringing call");
5620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
56322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.size() != 1) {
5640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall() in a conf call");
5650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
56622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            ((SipConnection) mConnections.get(0)).acceptCall();
5670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean isSpeakerOn() {
570f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Boolean ret = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
5710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    .isSpeakerphoneOn();
572f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_VDBG) log("isSpeakerOn: ret=" + ret);
573f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
5740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setAudioGroupMode() {
5770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
578f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
579f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("setAudioGroupMode: audioGroup == null ignore");
580f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
581f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
5820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int mode = audioGroup.getMode();
58322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState == State.HOLDING) {
5840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
5850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (getMute()) {
5860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_MUTED);
5870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else if (isSpeakerOn()) {
5880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
5890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } else {
5900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                audioGroup.setMode(AudioGroup.MODE_NORMAL);
5910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
592f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log(String.format(
593f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    "setAudioGroupMode change: %d --> %d", mode,
5940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    audioGroup.getMode()));
5950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
5960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
5970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
598f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("hold:");
5990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.HOLDING);
60022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) ((SipConnection) c).hold();
6010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
6020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold() throws CallStateException {
605f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("unhold:");
6060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(State.ACTIVE);
6070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = new AudioGroup();
60822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
6090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).unhold(audioGroup);
6100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setAudioGroupMode();
6120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
615f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("setMute: muted=" + muted);
61622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            for (Connection c : mConnections) {
6170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                ((SipConnection) c).setMute(muted);
6180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
62222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            boolean ret = mConnections.isEmpty()
6230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    ? false
62422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    : ((SipConnection) mConnections.get(0)).getMute();
625f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("getMute: ret=" + ret);
626f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            return ret;
6270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void merge(SipCall that) throws CallStateException {
630f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("merge:");
6310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
6320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // copy to an array to avoid concurrent modification as connections
6340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // in that.connections will be removed in add(SipConnection).
63522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            Connection[] cc = that.mConnections.toArray(
63622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    new Connection[that.mConnections.size()]);
6370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            for (Connection c : cc) {
6380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipConnection conn = (SipConnection) c;
6390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                add(conn);
6400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (conn.getState() == Call.State.HOLDING) {
6410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    conn.unhold(audioGroup);
6420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            that.setState(Call.State.IDLE);
6450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private void add(SipConnection conn) {
648f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("add:");
6490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            SipCall call = conn.getCall();
6500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (call == this) return;
65122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (call != null) call.mConnections.remove(conn);
6520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
65322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            mConnections.add(conn);
6540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            conn.changeOwner(this);
6550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void sendDtmf(char c) {
658f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("sendDtmf: c=" + c);
6590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            AudioGroup audioGroup = getAudioGroup();
660f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (audioGroup == null) {
661f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("sendDtmf: audioGroup == null, ignore c=" + c);
662f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                return;
663f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
6640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            audioGroup.sendDtmf(convertDtmf(c));
6650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private int convertDtmf(char c) {
6680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            int code = c - '0';
6690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((code < 0) || (code > 9)) {
6700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                switch (c) {
6710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '*': return 10;
6720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case '#': return 11;
6730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'A': return 12;
6740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'B': return 13;
6750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'C': return 14;
6760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    case 'D': return 15;
6770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    default:
6780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        throw new IllegalArgumentException(
6790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                "invalid DTMF char: " + (int) c);
6800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
6810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
6820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return code;
6830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
6840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
6860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(State newState) {
68722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != newState) {
68822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                if (SC_DBG) log("setState: cur state" + mState
6890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + " --> " + newState + ": " + this + ": on phone "
69022d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + getPhone() + " " + mConnections.size());
6910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
6920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (newState == Call.State.ALERTING) {
69322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    mState = newState; // need in ALERTING to enable ringback
69422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    startRingbackTone();
69522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                } else if (mState == Call.State.ALERTING) {
69622d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                    stopRingbackTone();
6970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
69822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                mState = newState;
6990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                updatePhoneState();
7000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                notifyPreciseCallStateChanged();
7010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionStateChanged(SipConnection conn) {
7050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // this can be called back when a conf call is formed
706f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionStateChanged: conn=" + conn);
70722d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.ACTIVE) {
7080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                setState(conn.getState());
7090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void onConnectionEnded(SipConnection conn) {
7130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // set state to DISCONNECTED only when all conns are disconnected
714f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SC_DBG) log("onConnectionEnded: conn=" + conn);
71522d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mState != State.DISCONNECTED) {
7160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                boolean allConnectionsDisconnected = true;
717f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SC_DBG) log("---check connections: "
71822d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        + mConnections.size());
71922d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                for (Connection c : mConnections) {
720f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SC_DBG) log("   state=" + c.getState() + ": "
7210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + c);
7220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (c.getState() != State.DISCONNECTED) {
7230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        allConnectionsDisconnected = false;
7240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        break;
7250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
7280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            notifyDisconnectP(conn);
7300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
7310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private AudioGroup getAudioGroup() {
73322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            if (mConnections.isEmpty()) return null;
73422d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville            return ((SipConnection) mConnections.get(0)).getAudioGroup();
7350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
736f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
737f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
738f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SC_TAG, s);
739f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
7400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
7410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private class SipConnection extends SipConnectionBase {
743f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SCN_TAG = "SipConnection";
744f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SCN_DBG = true;
745f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
7460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipCall mOwner;
7470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCall mSipAudioCall;
7480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private Call.State mState = Call.State.IDLE;
7490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipProfile mPeer;
7500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private boolean mIncoming = false;
751f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private String mOriginalNumber; // may be a PSTN number
7520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
7540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
755b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            protected void onCallEnded(int cause) {
7560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (getDisconnectCause() != DisconnectCause.LOCAL) {
7570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setDisconnectCause(cause);
7580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    setState(Call.State.DISCONNECTED);
7610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
762f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    // FIXME: This goes null and is synchronized, but many uses aren't sync'd
7630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mSipAudioCall = null;
7640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    String sessionState = (sipAudioCall == null)
7650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? ""
7660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : (sipAudioCall.getState() + ", ");
767f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    if (SCN_DBG) log("[SipAudioCallAdapter] onCallEnded: "
768b4350195add6b77d9352a41e1a8e5580a6a4816cSantos Cordon                            + hidePii(mPeer.getUriString()) + ": " + sessionState
7690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + "cause: " + getDisconnectCause() + ", on phone "
7700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + getPhone());
7710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
7720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
7730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.close();
7740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
7750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionEnded(SipConnection.this);
7760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
7770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallEstablished(SipAudioCall call) {
7810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
782f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.ACTIVE) call.startAudio();
7840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onCallHeld(SipAudioCall call) {
7880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onChanged(call);
789f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                // Race onChanged synchronized this isn't
7900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (mState == Call.State.HOLDING) call.startAudio();
7910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
7920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
7930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
7940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            public void onChanged(SipAudioCall call) {
7950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                synchronized (SipPhone.class) {
7960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    Call.State newState = getCallStateFrom(call);
7970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (mState == newState) return;
7980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (newState == Call.State.INCOMING) {
7990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(mOwner.getState()); // INCOMING or WAITING
8000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    } else {
80122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        if (mOwner == mRingingCall) {
80222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            if (mRingingCall.getState() == Call.State.WAITING) {
8030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                try {
8040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    switchHoldingAndActive();
8050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                } catch (CallStateException e) {
8060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    // disconnect the call.
8070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    onCallEnded(DisconnectCause.LOCAL);
8080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                    return;
8090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                }
8100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            }
81122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                            mForegroundCall.switchWith(mRingingCall);
8120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        }
8130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        setState(newState);
8140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
8150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mOwner.onConnectionStateChanged(SipConnection.this);
816f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                    if (SCN_DBG) {
817f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                        log("onChanged: " + hidePii(mPeer.getUriString()) + ": " + mState
818f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                                + " on phone " + getPhone());
819f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                    }
8200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
8210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            @Override
824b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            protected void onError(int cause) {
825f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("onError: " + cause);
8260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                onCallEnded(cause);
8270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        };
8290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee,
8310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String originalNumber) {
8320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super(originalNumber);
8330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mPeer = callee;
8350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOriginalNumber = originalNumber;
8360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipConnection(SipCall owner, SipProfile callee) {
8390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            this(owner, callee, getUriString(callee));
8400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getCnapName() {
8440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            String displayName = mPeer.getDisplayName();
8450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return TextUtils.isEmpty(displayName) ? null
8460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                                  : displayName;
8470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
8500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public int getNumberPresentation() {
8510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return PhoneConstants.PRESENTATION_ALLOWED;
8520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
8550825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(newState);
8560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = sipAudioCall;
8570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            sipAudioCall.setListener(mAdapter); // call back to set state
8580825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mIncoming = true;
8590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void acceptCall() throws CallStateException {
8620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
8640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("acceptCall(): " + e);
8660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void changeOwner(SipCall owner) {
8700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mOwner = owner;
8710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        AudioGroup getAudioGroup() {
8740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (mSipAudioCall == null) return null;
8750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mSipAudioCall.getAudioGroup();
8760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void dial() throws SipException {
8790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.DIALING);
8800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
8810825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    TIMEOUT_MAKE_CALL);
8820825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setListener(mAdapter);
8830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void hold() throws CallStateException {
8860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.HOLDING);
8870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8880825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
8890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
8900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("hold(): " + e);
8910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
8920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
8930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
8940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void unhold(AudioGroup audioGroup) throws CallStateException {
8950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mSipAudioCall.setAudioGroup(audioGroup);
8960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            setState(Call.State.ACTIVE);
8970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            try {
8980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
8990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            } catch (SipException e) {
9000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                throw new CallStateException("unhold(): " + e);
9010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9030825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        void setMute(boolean muted) {
9050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
906f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("setState: prev muted=" + !muted + " new muted=" + muted);
9070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.toggleMute();
9080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9090825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9100825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        boolean getMute() {
9120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return (mSipAudioCall == null) ? false
9130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                                           : mSipAudioCall.isMuted();
9140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9150825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9160825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9170825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected void setState(Call.State state) {
9180825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            if (state == mState) return;
9190825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            super.setState(state);
9200825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            mState = state;
9210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9240825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public Call.State getState() {
9250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mState;
9260825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9270825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public boolean isIncoming() {
9300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mIncoming;
9310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9320825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9330825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public String getAddress() {
9350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // Phone app uses this to query caller ID. Return the original dial
9360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // number (which may be a PSTN number) instead of the peer's SIP
9370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            // URI.
9380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOriginalNumber;
9390825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9400825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9410825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9420825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public SipCall getCall() {
9430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner;
9440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9450825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        protected Phone getPhone() {
9480825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            return mOwner.getPhone();
9490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9510825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void hangup() throws CallStateException {
9530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
954f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                if (SCN_DBG) {
955f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                    log("hangup: conn=" + hidePii(mPeer.getUriString())
956f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                            + ": " + mState + ": on phone "
957f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                            + getPhone().getPhoneName());
958f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai                }
9590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (!mState.isAlive()) return;
9600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                try {
9610825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    SipAudioCall sipAudioCall = mSipAudioCall;
9620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    if (sipAudioCall != null) {
9630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.setListener(null);
9640825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        sipAudioCall.endCall();
9650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    }
9660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } catch (SipException e) {
9670825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException("hangup(): " + e);
9680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                } finally {
9690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
9700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            || (mState == Call.State.WAITING))
9710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            ? DisconnectCause.INCOMING_REJECTED
9720825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            : DisconnectCause.LOCAL);
9730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
9740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
9750825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
9760825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9770825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
9780825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void separate() throws CallStateException {
9790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            synchronized (SipPhone.class) {
9800825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                SipCall call = (getPhone() == SipPhone.this)
98122d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        ? (SipCall) getBackgroundCall()
98222d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                        : (SipCall) getForegroundCall();
9830825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                if (call.getState() != Call.State.IDLE) {
9840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    throw new CallStateException(
9850825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            "cannot put conn back to a call in non-idle state: "
9860825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                            + call.getState());
9870825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                }
988f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                if (SCN_DBG) log("separate: conn="
9890825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + mPeer.getUriString() + " from " + mOwner + " back to "
9900825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                        + call);
9910825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9920825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // separate the AudioGroup and connection from the original call
9930825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                Phone originalPhone = getPhone();
9940825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                AudioGroup audioGroup = call.getAudioGroup(); // may be null
9950825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.add(this);
9960825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.setAudioGroup(audioGroup);
9970825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
9980825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // put the original call to bg; and the separated call becomes
9990825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // fg if it was in bg
10000825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                originalPhone.switchHoldingAndActive();
10010825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10020825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                // start audio and notify the phone app of the state change
100322d85a8e3a575a6d01d2c788587971657dfe20c6Wink Saville                call = (SipCall) getForegroundCall();
10040825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                mSipAudioCall.startAudio();
10050825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                call.onConnectionStateChanged(this);
10060825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
10070825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10080825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
1009f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
1010f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SCN_TAG, s);
10110825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10120825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
10130825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10140825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
1015f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final String SACA_TAG = "SipAudioCallAdapter";
1016f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private static final boolean SACA_DBG = true;
1017b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        /** Call ended with cause defined in {@link DisconnectCause}. */
1018b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        protected abstract void onCallEnded(int cause);
1019b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        /** Call failed with cause defined in {@link DisconnectCause}. */
1020b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen        protected abstract void onError(int cause);
10210825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10220825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10230825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallEnded(SipAudioCall call) {
1024f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallEnded: call=" + call);
10250825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            onCallEnded(call.isInCall()
1026b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    ? DisconnectCause.NORMAL
1027b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    : DisconnectCause.INCOMING_MISSED);
10280825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10290825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10300825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10310825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onCallBusy(SipAudioCall call) {
1032f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) log("onCallBusy: call=" + call);
1033b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen            onCallEnded(DisconnectCause.BUSY);
10340825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
10350825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville
10360825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        @Override
10370825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        public void onError(SipAudioCall call, int errorCode,
10380825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                String errorMessage) {
1039f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            if (SACA_DBG) {
1040f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                log("onError: call=" + call + " code="+ SipErrorCode.toString(errorCode)
1041f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville                    + ": " + errorMessage);
1042f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            }
10430825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            switch (errorCode) {
10440825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_UNREACHABLE:
1045b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.SERVER_UNREACHABLE);
10460825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10470825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.PEER_NOT_REACHABLE:
1048b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.NUMBER_UNREACHABLE);
10490825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10500825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_REMOTE_URI:
1051b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.INVALID_NUMBER);
10520825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10530825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TIME_OUT:
10540825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.TRANSACTION_TERMINTED:
1055b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.TIMED_OUT);
10560825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10570825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.DATA_CONNECTION_LOST:
1058b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.LOST_SIGNAL);
10590825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10600825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.INVALID_CREDENTIALS:
1061b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.INVALID_CREDENTIALS);
10620825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10630825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
1064b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.OUT_OF_NETWORK);
10650825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10660825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SERVER_ERROR:
1067b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.SERVER_ERROR);
10680825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                    break;
10690825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.SOCKET_ERROR:
10700825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                case SipErrorCode.CLIENT_ERROR:
10710825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville                default:
1072b7b7a62112b79571adf74372c5f5366fd62d0031Anders Kristensen                    onError(DisconnectCause.ERROR_UNSPECIFIED);
10730825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville            }
10740825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville        }
1075f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville
1076f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        private void log(String s) {
1077f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville            Rlog.d(SACA_TAG, s);
1078f1ac06f0498ec7cb7489c835bcd2eed568b5f6a6Wink Saville        }
10790825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville    }
1080b4350195add6b77d9352a41e1a8e5580a6a4816cSantos Cordon
1081b4350195add6b77d9352a41e1a8e5580a6a4816cSantos Cordon    public static String hidePii(String s) {
1082f0e497e8cf789cf47bd60a68ac12e5e22dbbe855Shunta Sakai        return VDBG ? Rlog.pii(LOG_TAG, s) : "xxxxx";
1083b4350195add6b77d9352a41e1a8e5580a6a4816cSantos Cordon    }
10840825495a331bb44df395a0cdb79fab85e68db5d5Wink Saville}
1085