SipPhone.java revision b7b7a62112b79571adf74372c5f5366fd62d0031
1c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet/*
2c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * Copyright (C) 2010 The Android Open Source Project
3c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet *
4c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * Licensed under the Apache License, Version 2.0 (the "License");
5c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * you may not use this file except in compliance with the License.
6c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * You may obtain a copy of the License at
7c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet *
8c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet *      http://www.apache.org/licenses/LICENSE-2.0
9c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet *
10c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * Unless required by applicable law or agreed to in writing, software
11c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * distributed under the License is distributed on an "AS IS" BASIS,
12c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * See the License for the specific language governing permissions and
14c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet * limitations under the License.
15c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet */
16c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
1732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungpackage com.android.internal.telephony.sip;
18c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
19c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.content.Context;
20c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.media.AudioManager;
21c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.rtp.AudioGroup;
22c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.sip.SipAudioCall;
23c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.sip.SipErrorCode;
24c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.sip.SipException;
25c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.sip.SipManager;
26c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.sip.SipProfile;
27c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.net.sip.SipSession;
28c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.os.AsyncResult;
29c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.os.Message;
30c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.telephony.DisconnectCause;
31c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouilletimport android.telephony.PhoneNumberUtils;
322217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouilletimport android.telephony.ServiceState;
332217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouilletimport android.text.TextUtils;
3432d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport android.telephony.Rlog;
3532d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
3632d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport com.android.internal.telephony.Call;
3732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport com.android.internal.telephony.CallStateException;
3832d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport com.android.internal.telephony.Connection;
3932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport com.android.internal.telephony.Phone;
4032d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport com.android.internal.telephony.PhoneConstants;
4132d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport com.android.internal.telephony.PhoneNotifier;
4232d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
4332d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport java.text.ParseException;
4432d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport java.util.List;
4532d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungimport java.util.regex.Pattern;
4632d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
4732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung/**
4832d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung * {@hide}
4932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung */
5032d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sungpublic class SipPhone extends SipPhoneBase {
5132d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private static final String LOG_TAG = "SipPhone";
5232d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private static final boolean DBG = true;
536e6b65617d3a779d939e7d6dd26663a898205ee3I-Jui (Ray) Sung    private static final boolean VDBG = false; // STOPSHIP if true
5432d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
5532d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
5632d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
5732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
582217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet    // A call that is ringing or (call) waiting
5932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private SipCall mRingingCall = new SipCall();
6032d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private SipCall mForegroundCall = new SipCall();
61918944e41306198807728ae3f05a1799773895c5Jean-Luc Brouillet    private SipCall mBackgroundCall = new SipCall();
62c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
63c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private SipManager mSipManager;
6432d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    private SipProfile mProfile;
65918944e41306198807728ae3f05a1799773895c5Jean-Luc Brouillet
6632d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
6732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        super("SIP:" + profile.getUriString(), context, notifier);
68c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
69c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (DBG) log("new SipPhone: " + profile.getUriString());
70c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        mRingingCall = new SipCall();
71c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        mForegroundCall = new SipCall();
72c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        mBackgroundCall = new SipCall();
73c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        mProfile = profile;
74c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        mSipManager = SipManager.newInstance(context);
75c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
76c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
77c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
78c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public boolean equals(Object o) {
79c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (o == this) return true;
80c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (!(o instanceof SipPhone)) return false;
81c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        SipPhone that = (SipPhone) o;
82c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        return mProfile.getUriString().equals(that.mProfile.getUriString());
83c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
84c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
85c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public String getSipUri() {
86c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        return mProfile.getUriString();
87c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
88c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
89c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public boolean equals(SipPhone phone) {
90c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        return getSipUri().equals(phone.getSipUri());
91c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
92c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
93c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public boolean canTake(Object incomingCall) {
94c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // FIXME: Is synchronizing on the class necessary, should we use a mLockObj?
95c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // Also there are many things not synchronized, of course
96c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // this may be true of CdmaPhone and GsmPhone too!!!
97c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        synchronized (SipPhone.class) {
98c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (!(incomingCall instanceof SipAudioCall)) {
99c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) log("canTake: ret=false, not a SipAudioCall");
100c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return false;
101c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
102c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mRingingCall.getState().isAlive()) {
103c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) log("canTake: ret=false, ringingCall not alive");
104c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return false;
105c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
106c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
107c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            // FIXME: is it true that we cannot take any incoming call if
108c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            // both foreground and background are active
109c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mForegroundCall.getState().isAlive()
110c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    && mBackgroundCall.getState().isAlive()) {
111c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) {
112c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    log("canTake: ret=false," +
113c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                            " foreground and background both alive");
114c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
115c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return false;
116c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
117c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
118c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            try {
119c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
120c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) log("canTake: taking call from: "
121c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        + sipAudioCall.getPeerProfile().getUriString());
122c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                String localUri = sipAudioCall.getLocalProfile().getUriString();
123c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (localUri.equals(mProfile.getUriString())) {
124c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    boolean makeCallWait = mForegroundCall.getState().isAlive();
125c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    mRingingCall.initIncomingCall(sipAudioCall, makeCallWait);
126c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    if (sipAudioCall.getState()
127c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                            != SipSession.State.INCOMING_CALL) {
128c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        // Peer cancelled the call!
129c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        if (DBG) log("    canTake: call cancelled !!");
130c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        mRingingCall.reset();
131c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    }
132c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    return true;
133c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
134c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } catch (Exception e) {
135c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                // Peer may cancel the call at any time during the time we hook
136c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                // up ringingCall with sipAudioCall. Clean up ringingCall when
137c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                // that happens.
138c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) log("    canTake: exception e=" + e);
139c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                mRingingCall.reset();
140c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
141c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (DBG) log("canTake: NOT taking !!");
142c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return false;
143c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
144c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
145c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
146c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
147c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public void acceptCall() throws CallStateException {
148c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        synchronized (SipPhone.class) {
149c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if ((mRingingCall.getState() == Call.State.INCOMING) ||
150c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    (mRingingCall.getState() == Call.State.WAITING)) {
151c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) log("acceptCall: accepting");
152c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                // Always unmute when answering a new call
153c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                mRingingCall.setMute(false);
154c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                mRingingCall.acceptCall();
155c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } else {
156c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) {
157c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    log("acceptCall:" +
158c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        " throw CallStateException(\"phone not ringing\")");
159c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
160c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                throw new CallStateException("phone not ringing");
161c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
162c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
163c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
164c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
165c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
166c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public void rejectCall() throws CallStateException {
167c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        synchronized (SipPhone.class) {
168c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mRingingCall.getState().isRinging()) {
169c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) log("rejectCall: rejecting");
170c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                mRingingCall.rejectCall();
171c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } else {
172c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (DBG) {
173c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    log("rejectCall:" +
174c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        " throw CallStateException(\"phone not ringing\")");
175c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
176c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                throw new CallStateException("phone not ringing");
177c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
178c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
179c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
180c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
181c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
182c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public Connection dial(String dialString) throws CallStateException {
183c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        synchronized (SipPhone.class) {
184c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return dialInternal(dialString);
185c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
186c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
187c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
188c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private Connection dialInternal(String dialString)
189c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            throws CallStateException {
190c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (DBG) log("dialInternal: dialString=" + (VDBG ? dialString : "xxxxxx"));
191c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        clearDisconnected();
192c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
193c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (!canDial()) {
194c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            throw new CallStateException("dialInternal: cannot dial in current state");
195c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
196c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (mForegroundCall.getState() == SipCall.State.ACTIVE) {
197c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            switchHoldingAndActive();
198c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
199c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (mForegroundCall.getState() != SipCall.State.IDLE) {
200c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            //we should have failed in !canDial() above before we get here
201c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            throw new CallStateException("cannot dial in current state");
202c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
203c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
204c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        mForegroundCall.setMute(false);
205c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        try {
206c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            Connection c = mForegroundCall.dial(dialString);
207c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return c;
208c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        } catch (SipException e) {
209c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            loge("dialInternal: ", e);
210c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            throw new CallStateException("dial error: " + e);
211c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
212c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
213c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
214c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
215c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public void switchHoldingAndActive() throws CallStateException {
216c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (DBG) log("dialInternal: switch fg and bg");
217c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        synchronized (SipPhone.class) {
218c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mForegroundCall.switchWith(mBackgroundCall);
219c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mBackgroundCall.getState().isAlive()) mBackgroundCall.hold();
220c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mForegroundCall.getState().isAlive()) mForegroundCall.unhold();
221c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
222c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
223c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
224c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
225c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public boolean canConference() {
226c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (DBG) log("canConference: ret=true");
227c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        return true;
228c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
2294a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
230c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
2316386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet    public void conference() throws CallStateException {
232c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        synchronized (SipPhone.class) {
2334a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            if ((mForegroundCall.getState() != SipCall.State.ACTIVE)
234c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    || (mForegroundCall.getState() != SipCall.State.ACTIVE)) {
2354a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                throw new CallStateException("wrong state to merge calls: fg="
236c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        + mForegroundCall.getState() + ", bg="
237c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        + mBackgroundCall.getState());
238c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
239c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (DBG) log("conference: merge fg & bg");
240c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mForegroundCall.merge(mBackgroundCall);
241c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
242c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
2434a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
2444a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void conference(Call that) throws CallStateException {
2454a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        synchronized (SipPhone.class) {
2464a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            if (!(that instanceof SipCall)) {
2474a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                throw new CallStateException("expect " + SipCall.class
2484a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                        + ", cannot merge with " + that.getClass());
2494a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            }
2504a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            mForegroundCall.merge((SipCall) that);
2514a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        }
252c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
253c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
2544a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    @Override
2554a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public boolean canTransfer() {
2564a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        return false;
257c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
258c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
2594a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    @Override
2604a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void explicitCallTransfer() {
2614a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        //mCT.explicitCallTransfer();
2622217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet    }
2632217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet
2644a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    @Override
2654a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void clearDisconnected() {
2664a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        synchronized (SipPhone.class) {
2674a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            mRingingCall.clearDisconnected();
2684a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            mForegroundCall.clearDisconnected();
2694a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            mBackgroundCall.clearDisconnected();
2704a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
2714a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            updatePhoneState();
2724a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            notifyPreciseCallStateChanged();
273c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
274c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
2754a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
2764a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    @Override
2774a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void sendDtmf(char c) {
2784a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        if (!PhoneNumberUtils.is12Key(c)) {
2794a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            loge("sendDtmf called with invalid character '" + c + "'");
2804a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        } else if (mForegroundCall.getState().isAlive()) {
2814a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            synchronized (SipPhone.class) {
2824a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                mForegroundCall.sendDtmf(c);
2834a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            }
2844a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        }
2854a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
2864a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
287c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
288c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public void startDtmf(char c) {
289c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (!PhoneNumberUtils.is12Key(c)) {
290c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            loge("startDtmf called with invalid character '" + c + "'");
2914a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        } else {
2924a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            sendDtmf(c);
2934a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        }
2944a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
2954a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
296c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
2974a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void stopDtmf() {
298c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // no op
2994a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
3004a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
3014a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void sendBurstDtmf(String dtmfString) {
302c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        loge("sendBurstDtmf() is a CDMA method");
3034a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
304c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
3054a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    @Override
3064a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void getOutgoingCallerIdDisplay(Message onComplete) {
3074a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        // FIXME: what to reply?
308c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        AsyncResult.forMessage(onComplete, null, null);
3094a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        onComplete.sendToTarget();
310c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
311c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
312ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    @Override
313ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
3144a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                                           Message onComplete) {
315c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // FIXME: what's this for SIP?
3164a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        AsyncResult.forMessage(onComplete, null, null);
317c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        onComplete.sendToTarget();
3184a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
319c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
320c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
321c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public void getCallWaiting(Message onComplete) {
322c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // FIXME: what to reply?
323c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        AsyncResult.forMessage(onComplete, null, null);
324c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        onComplete.sendToTarget();
325c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
326c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
327c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
328c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    public void setCallWaiting(boolean enable, Message onComplete) {
329ffc1710c382168e74386ef87d0101de65aca91b0Jean-Luc Brouillet        // FIXME: what to reply?
330c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        loge("call waiting not supported");
331c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
332c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
333c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
3344a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void setEchoSuppressionEnabled(boolean enabled) {
335c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // TODO: Remove the enabled argument. We should check the speakerphone
3364a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        // state with AudioManager instead of keeping a state here so the
337c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // method with a state argument is redundant. Also rename the method
338c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // to something like onSpeaerphoneStateChanged(). Echo suppression may
3394a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        // not be available on every device.
3404a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        synchronized (SipPhone.class) {
341ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines            mForegroundCall.setAudioGroupMode();
342ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines        }
343ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    }
344ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines
345ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    @Override
3464a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public void setMute(boolean muted) {
347ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines        synchronized (SipPhone.class) {
348c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mForegroundCall.setMute(muted);
349c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
350c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
35136e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet
35236e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet    @Override
35336e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet    public boolean getMute() {
35436e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet        return (mForegroundCall.getState().isAlive()
35536e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet                ? mForegroundCall.getMute()
356ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines                : mBackgroundCall.getMute());
35736e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet    }
3584a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
359ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    @Override
360ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    public Call getForegroundCall() {
361ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines        return mForegroundCall;
362ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    }
363ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines
364ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    @Override
365ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    public Call getBackgroundCall() {
3664a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        return mBackgroundCall;
367c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
36836e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet
369c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    @Override
3704a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    public Call getRingingCall() {
371ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines        return mRingingCall;
3724a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
373c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
3744a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    @Override
37536e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet    public ServiceState getServiceState() {
37636e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet        // FIXME: we may need to provide this when data connectivity is lost
377c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        // or when server is down
3784a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        return super.getServiceState();
3794a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
3804a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
381c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private String getUriString(SipProfile p) {
3824a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        // SipProfile.getUriString() may contain "SIP:" and port
383c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        return p.getUserName() + "@" + getSipDomain(p);
3844a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
385c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
386c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private String getSipDomain(SipProfile p) {
387c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        String domain = p.getSipDomain();
3884a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        // TODO: move this to SipProfile
389ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines        if (domain.endsWith(":5060")) {
3904a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            return domain.substring(0, domain.length() - 5);
3914a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        } else {
3924a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            return domain;
39336e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet        }
39436e2be56cd398bf4a318114bbc9fa3f4573c158fJean-Luc Brouillet    }
395c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
3964a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
397c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
3984a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        int sessionState = sipAudioCall.getState();
399c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        switch (sessionState) {
4004a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
401c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            case SipSession.State.INCOMING_CALL:
4024a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
403c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
404c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
405c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
406c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
407c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            default:
4084a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                slog("illegal connection state: " + sessionState);
4094a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                return Call.State.DISCONNECTED;
410c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
411ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines    }
4124a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
4134a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    private void log(String s) {
414c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        Rlog.d(LOG_TAG, s);
415c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
416c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
417c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private static void slog(String s) {
418c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        Rlog.d(LOG_TAG, s);
419c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
420c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
421c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private void loge(String s) {
422c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        Rlog.e(LOG_TAG, s);
423c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    }
424c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
4257c07854a959eb70ff9623202b2ca064407a1cc68Jean-Luc Brouillet    private void loge(String s, Exception e) {
4264a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        Rlog.e(LOG_TAG, s, e);
4274a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet    }
428c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
429c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet    private class SipCall extends SipCallBase {
430c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        private static final String SC_TAG = "SipCall";
43132d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        private static final boolean SC_DBG = true;
432c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        private static final boolean SC_VDBG = false; // STOPSHIP if true
43332d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
434c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void reset() {
435c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("reset");
436c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mConnections.clear();
437c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            setState(Call.State.IDLE);
43832d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        }
43932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
44032d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        void switchWith(SipCall that) {
44132d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung            if (SC_DBG) log("switchWith");
442c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            synchronized (SipPhone.class) {
443c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                SipCall tmp = new SipCall();
444c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                tmp.takeOver(this);
445c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                this.takeOver(that);
446c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                that.takeOver(tmp);
447c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
448c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
44932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung
450c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        private void takeOver(SipCall that) {
451c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("takeOver");
452c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mConnections = that.mConnections;
453c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mState = that.mState;
45432d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung            for (Connection c : mConnections) {
455c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                ((SipConnection) c).changeOwner(this);
45632d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung            }
457c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
458c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
45932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        @Override
460c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        public Phone getPhone() {
461c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return SipPhone.this;
4624a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        }
4634a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
4644a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        @Override
4654a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        public List<Connection> getConnections() {
466c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_VDBG) log("getConnections");
46732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung            synchronized (SipPhone.class) {
468c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                // FIXME should return Collections.unmodifiableList();
469c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return mConnections;
470c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
471c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
4724a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
4734a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        Connection dial(String originalNumber) throws SipException {
4744a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            if (SC_DBG) log("dial: num=" + (SC_VDBG ? originalNumber : "xxx"));
4754a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            // TODO: Should this be synchronized?
4764a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            String calleeSipUri = originalNumber;
4774a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            if (!calleeSipUri.contains("@")) {
4784a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
4794a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
4804a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                        calleeSipUri + "@");
4814a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            }
4824a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            try {
4834a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                SipProfile callee =
4844a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                        new SipProfile.Builder(calleeSipUri).build();
4854a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                SipConnection c = new SipConnection(this, callee,
486c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        originalNumber);
487c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                c.dial();
488c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                mConnections.add(c);
489c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                setState(Call.State.DIALING);
490c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return c;
491c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } catch (ParseException e) {
492918944e41306198807728ae3f05a1799773895c5Jean-Luc Brouillet                throw new SipException("dial", e);
493c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
494c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
495c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
496c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        @Override
497c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        public void hangup() throws CallStateException {
498c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            synchronized (SipPhone.class) {
499c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (mState.isAlive()) {
500c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    if (SC_DBG) log("hangup: call " + getState()
5014a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                            + ": " + this + " on phone " + getPhone());
5024a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                    setState(State.DISCONNECTING);
5034a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                    CallStateException excp = null;
5044a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                    for (Connection c : mConnections) {
5054a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                        try {
5064a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                            c.hangup();
5074a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                        } catch (CallStateException e) {
508c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                            excp = e;
509c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        }
510c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    }
511c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    if (excp != null) throw excp;
512c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                } else {
513c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    if (SC_DBG) log("hangup: dead call " + getState()
5144a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                            + ": " + this + " on phone " + getPhone());
515c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
516c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
517c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
518c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
519c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
520c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            SipProfile callee = sipAudioCall.getPeerProfile();
521c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            SipConnection c = new SipConnection(this, callee);
522c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mConnections.add(c);
523c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
524c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
525c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            c.initIncomingCall(sipAudioCall, newState);
526c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
527c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            setState(newState);
528c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            notifyNewRingingConnectionP(c);
529c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
530c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
531918944e41306198807728ae3f05a1799773895c5Jean-Luc Brouillet        void rejectCall() throws CallStateException {
532c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("rejectCall:");
533c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            hangup();
534c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
535c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
536c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void acceptCall() throws CallStateException {
537c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("acceptCall: accepting");
538c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (this != mRingingCall) {
5394a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                throw new CallStateException("acceptCall() in a non-ringing call");
5404a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            }
541c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mConnections.size() != 1) {
542c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                throw new CallStateException("acceptCall() in a conf call");
5434a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            }
544c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            ((SipConnection) mConnections.get(0)).acceptCall();
545c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
546c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
547c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        private boolean isSpeakerOn() {
5484a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            Boolean ret = ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
549c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    .isSpeakerphoneOn();
550c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_VDBG) log("isSpeakerOn: ret=" + ret);
551c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return ret;
552c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
553c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
554c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void setAudioGroupMode() {
5556386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet            AudioGroup audioGroup = getAudioGroup();
5566386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet            if (audioGroup == null) {
5576386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet                if (SC_DBG) log("setAudioGroupMode: audioGroup == null ignore");
558c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return;
559c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
560918944e41306198807728ae3f05a1799773895c5Jean-Luc Brouillet            int mode = audioGroup.getMode();
561c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mState == State.HOLDING) {
562c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
563c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } else if (getMute()) {
564c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                audioGroup.setMode(AudioGroup.MODE_MUTED);
565c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } else if (isSpeakerOn()) {
566c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
567c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            } else {
568c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                audioGroup.setMode(AudioGroup.MODE_NORMAL);
569c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
570c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log(String.format(
571c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    "setAudioGroupMode change: %d --> %d", mode,
5724a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet                    audioGroup.getMode()));
573c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
574c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
5754a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        void hold() throws CallStateException {
576c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("hold:");
577ca51c78b9e3097ee31dd24cdc5982f550ee563d1Stephen Hines            setState(State.HOLDING);
5784a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            for (Connection c : mConnections) ((SipConnection) c).hold();
579c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            setAudioGroupMode();
580c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
581c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
582c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void unhold() throws CallStateException {
583c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("unhold:");
584c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            setState(State.ACTIVE);
585c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            AudioGroup audioGroup = new AudioGroup();
586c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            for (Connection c : mConnections) {
587c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                ((SipConnection) c).unhold(audioGroup);
588c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
589c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            setAudioGroupMode();
590c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
591c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
592c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void setMute(boolean muted) {
593c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("setMute: muted=" + muted);
594c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            for (Connection c : mConnections) {
595c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                ((SipConnection) c).setMute(muted);
596c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
597c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
598c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
599c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        boolean getMute() {
600c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            boolean ret = mConnections.isEmpty()
601c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    ? false
602c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    : ((SipConnection) mConnections.get(0)).getMute();
603c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("getMute: ret=" + ret);
604c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return ret;
605c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
6064a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet
607c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void merge(SipCall that) throws CallStateException {
608c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("merge:");
609c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            AudioGroup audioGroup = getAudioGroup();
610c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
611c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            // copy to an array to avoid concurrent modification as connections
612c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            // in that.connections will be removed in add(SipConnection).
613c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            Connection[] cc = that.mConnections.toArray(
614c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    new Connection[that.mConnections.size()]);
615c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            for (Connection c : cc) {
616c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                SipConnection conn = (SipConnection) c;
61732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung                add(conn);
61832d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung                if (conn.getState() == Call.State.HOLDING) {
6192217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                    conn.unhold(audioGroup);
6202217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                }
6212217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            }
6222217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            that.setState(Call.State.IDLE);
6232217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        }
624c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
6252217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        private void add(SipConnection conn) {
62632d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung            if (SC_DBG) log("add:");
6272217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            SipCall call = conn.getCall();
628c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (call == this) return;
629c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (call != null) call.mConnections.remove(conn);
630c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
631c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            mConnections.add(conn);
6322217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            conn.changeOwner(this);
63332d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        }
634c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
635c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void sendDtmf(char c) {
636c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("sendDtmf: c=" + c);
637c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            AudioGroup audioGroup = getAudioGroup();
638c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (audioGroup == null) {
639c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (SC_DBG) log("sendDtmf: audioGroup == null, ignore c=" + c);
640c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                return;
641c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
6427c07854a959eb70ff9623202b2ca064407a1cc68Jean-Luc Brouillet            audioGroup.sendDtmf(convertDtmf(c));
6437c07854a959eb70ff9623202b2ca064407a1cc68Jean-Luc Brouillet        }
6447c07854a959eb70ff9623202b2ca064407a1cc68Jean-Luc Brouillet
6454a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet        private int convertDtmf(char c) {
6464a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            int code = c - '0';
6474a73004df5231d188c41267fee17c566ae7c3631Jean-Luc Brouillet            if ((code < 0) || (code > 9)) {
648c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                switch (c) {
649c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    case '*': return 10;
650c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    case '#': return 11;
651c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    case 'A': return 12;
652c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    case 'B': return 13;
653c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    case 'C': return 14;
654c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    case 'D': return 15;
655c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    default:
656c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        throw new IllegalArgumentException(
657c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                                "invalid DTMF char: " + (int) c);
658c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
659c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
660c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            return code;
661c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        }
662c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
663c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        @Override
664c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        protected void setState(State newState) {
665c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (mState != newState) {
666c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (SC_DBG) log("setState: cur state" + mState
667c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        + " --> " + newState + ": " + this + ": on phone "
668c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                        + getPhone() + " " + mConnections.size());
669c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
670c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (newState == Call.State.ALERTING) {
671c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    mState = newState; // need in ALERTING to enable ringback
672c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    startRingbackTone();
673c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                } else if (mState == Call.State.ALERTING) {
674c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    stopRingbackTone();
67532d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung                }
676c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                mState = newState;
677c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                updatePhoneState();
678c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                notifyPreciseCallStateChanged();
679c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            }
6802217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        }
681c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet
682c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        void onConnectionStateChanged(SipConnection conn) {
683c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            // this can be called back when a conf call is formed
684c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            if (SC_DBG) log("onConnectionStateChanged: conn=" + conn);
6852217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            if (mState != State.ACTIVE) {
6862217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                setState(conn.getState());
6872217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            }
6882217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        }
6892217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet
6902217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        void onConnectionEnded(SipConnection conn) {
6912217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            // set state to DISCONNECTED only when all conns are disconnected
6922217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            if (SC_DBG) log("onConnectionEnded: conn=" + conn);
6932217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            if (mState != State.DISCONNECTED) {
6942217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                boolean allConnectionsDisconnected = true;
6952217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                if (SC_DBG) log("---check connections: "
6962217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                        + mConnections.size());
6972217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                for (Connection c : mConnections) {
6982217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                    if (SC_DBG) log("   state=" + c.getState() + ": "
6992217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                            + c);
7002217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                    if (c.getState() != State.DISCONNECTED) {
7012217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                        allConnectionsDisconnected = false;
7022217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                        break;
7032217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet                    }
7046386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet                }
7056386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
7066386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet            }
7076386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet            notifyDisconnectP(conn);
7086386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet        }
7096386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet
7106386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet        private AudioGroup getAudioGroup() {
7116386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet            if (mConnections.isEmpty()) return null;
7126386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet            return ((SipConnection) mConnections.get(0)).getAudioGroup();
7132217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        }
7142217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet
7152217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        private void log(String s) {
7162217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet            Rlog.d(SC_TAG, s);
7172217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        }
7182217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet    }
7192217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet
7206386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet    private class SipConnection extends SipConnectionBase {
7216386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet        private static final String SCN_TAG = "SipConnection";
7226386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet        private static final boolean SCN_DBG = true;
7236386ceb3bf25e442513224aaa45691dfe49562d9Jean-Luc Brouillet
7242217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        private SipCall mOwner;
7252217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        private SipAudioCall mSipAudioCall;
7262217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet        private Call.State mState = Call.State.IDLE;
72732d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        private SipProfile mPeer;
72832d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        private boolean mIncoming = false;
72932d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung        private String mOriginalNumber; // may be a PSTN number
7302217eb7b12e598e5b435a732207647918c171560Jean-Luc Brouillet
731c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
73232d3520f388756d384fb1a130fd1142b8481f190I-Jui (Ray) Sung            @Override
733c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet            protected void onCallEnded(int cause) {
734c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                if (getDisconnectCause() != DisconnectCause.LOCAL) {
735c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                    setDisconnectCause(cause);
736c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                }
737c5184e202ced435258adb2cfe2013570e7190954Jean-Luc Brouillet                synchronized (SipPhone.class) {
738                    setState(Call.State.DISCONNECTED);
739                    SipAudioCall sipAudioCall = mSipAudioCall;
740                    // FIXME: This goes null and is synchronized, but many uses aren't sync'd
741                    mSipAudioCall = null;
742                    String sessionState = (sipAudioCall == null)
743                            ? ""
744                            : (sipAudioCall.getState() + ", ");
745                    if (SCN_DBG) log("[SipAudioCallAdapter] onCallEnded: "
746                            + mPeer.getUriString() + ": " + sessionState
747                            + "cause: " + getDisconnectCause() + ", on phone "
748                            + getPhone());
749                    if (sipAudioCall != null) {
750                        sipAudioCall.setListener(null);
751                        sipAudioCall.close();
752                    }
753                    mOwner.onConnectionEnded(SipConnection.this);
754                }
755            }
756
757            @Override
758            public void onCallEstablished(SipAudioCall call) {
759                onChanged(call);
760                // Race onChanged synchronized this isn't
761                if (mState == Call.State.ACTIVE) call.startAudio();
762            }
763
764            @Override
765            public void onCallHeld(SipAudioCall call) {
766                onChanged(call);
767                // Race onChanged synchronized this isn't
768                if (mState == Call.State.HOLDING) call.startAudio();
769            }
770
771            @Override
772            public void onChanged(SipAudioCall call) {
773                synchronized (SipPhone.class) {
774                    Call.State newState = getCallStateFrom(call);
775                    if (mState == newState) return;
776                    if (newState == Call.State.INCOMING) {
777                        setState(mOwner.getState()); // INCOMING or WAITING
778                    } else {
779                        if (mOwner == mRingingCall) {
780                            if (mRingingCall.getState() == Call.State.WAITING) {
781                                try {
782                                    switchHoldingAndActive();
783                                } catch (CallStateException e) {
784                                    // disconnect the call.
785                                    onCallEnded(DisconnectCause.LOCAL);
786                                    return;
787                                }
788                            }
789                            mForegroundCall.switchWith(mRingingCall);
790                        }
791                        setState(newState);
792                    }
793                    mOwner.onConnectionStateChanged(SipConnection.this);
794                    if (SCN_DBG) log("onChanged: "
795                            + mPeer.getUriString() + ": " + mState
796                            + " on phone " + getPhone());
797                }
798            }
799
800            @Override
801            protected void onError(int cause) {
802                if (SCN_DBG) log("onError: " + cause);
803                onCallEnded(cause);
804            }
805        };
806
807        public SipConnection(SipCall owner, SipProfile callee,
808                String originalNumber) {
809            super(originalNumber);
810            mOwner = owner;
811            mPeer = callee;
812            mOriginalNumber = originalNumber;
813        }
814
815        public SipConnection(SipCall owner, SipProfile callee) {
816            this(owner, callee, getUriString(callee));
817        }
818
819        @Override
820        public String getCnapName() {
821            String displayName = mPeer.getDisplayName();
822            return TextUtils.isEmpty(displayName) ? null
823                                                  : displayName;
824        }
825
826        @Override
827        public int getNumberPresentation() {
828            return PhoneConstants.PRESENTATION_ALLOWED;
829        }
830
831        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
832            setState(newState);
833            mSipAudioCall = sipAudioCall;
834            sipAudioCall.setListener(mAdapter); // call back to set state
835            mIncoming = true;
836        }
837
838        void acceptCall() throws CallStateException {
839            try {
840                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
841            } catch (SipException e) {
842                throw new CallStateException("acceptCall(): " + e);
843            }
844        }
845
846        void changeOwner(SipCall owner) {
847            mOwner = owner;
848        }
849
850        AudioGroup getAudioGroup() {
851            if (mSipAudioCall == null) return null;
852            return mSipAudioCall.getAudioGroup();
853        }
854
855        void dial() throws SipException {
856            setState(Call.State.DIALING);
857            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
858                    TIMEOUT_MAKE_CALL);
859            mSipAudioCall.setListener(mAdapter);
860        }
861
862        void hold() throws CallStateException {
863            setState(Call.State.HOLDING);
864            try {
865                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
866            } catch (SipException e) {
867                throw new CallStateException("hold(): " + e);
868            }
869        }
870
871        void unhold(AudioGroup audioGroup) throws CallStateException {
872            mSipAudioCall.setAudioGroup(audioGroup);
873            setState(Call.State.ACTIVE);
874            try {
875                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
876            } catch (SipException e) {
877                throw new CallStateException("unhold(): " + e);
878            }
879        }
880
881        void setMute(boolean muted) {
882            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
883                if (SCN_DBG) log("setState: prev muted=" + !muted + " new muted=" + muted);
884                mSipAudioCall.toggleMute();
885            }
886        }
887
888        boolean getMute() {
889            return (mSipAudioCall == null) ? false
890                                           : mSipAudioCall.isMuted();
891        }
892
893        @Override
894        protected void setState(Call.State state) {
895            if (state == mState) return;
896            super.setState(state);
897            mState = state;
898        }
899
900        @Override
901        public Call.State getState() {
902            return mState;
903        }
904
905        @Override
906        public boolean isIncoming() {
907            return mIncoming;
908        }
909
910        @Override
911        public String getAddress() {
912            // Phone app uses this to query caller ID. Return the original dial
913            // number (which may be a PSTN number) instead of the peer's SIP
914            // URI.
915            return mOriginalNumber;
916        }
917
918        @Override
919        public SipCall getCall() {
920            return mOwner;
921        }
922
923        @Override
924        protected Phone getPhone() {
925            return mOwner.getPhone();
926        }
927
928        @Override
929        public void hangup() throws CallStateException {
930            synchronized (SipPhone.class) {
931                if (SCN_DBG) log("hangup: conn=" + mPeer.getUriString()
932                        + ": " + mState + ": on phone "
933                        + getPhone().getPhoneName());
934                if (!mState.isAlive()) return;
935                try {
936                    SipAudioCall sipAudioCall = mSipAudioCall;
937                    if (sipAudioCall != null) {
938                        sipAudioCall.setListener(null);
939                        sipAudioCall.endCall();
940                    }
941                } catch (SipException e) {
942                    throw new CallStateException("hangup(): " + e);
943                } finally {
944                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
945                            || (mState == Call.State.WAITING))
946                            ? DisconnectCause.INCOMING_REJECTED
947                            : DisconnectCause.LOCAL);
948                }
949            }
950        }
951
952        @Override
953        public void separate() throws CallStateException {
954            synchronized (SipPhone.class) {
955                SipCall call = (getPhone() == SipPhone.this)
956                        ? (SipCall) getBackgroundCall()
957                        : (SipCall) getForegroundCall();
958                if (call.getState() != Call.State.IDLE) {
959                    throw new CallStateException(
960                            "cannot put conn back to a call in non-idle state: "
961                            + call.getState());
962                }
963                if (SCN_DBG) log("separate: conn="
964                        + mPeer.getUriString() + " from " + mOwner + " back to "
965                        + call);
966
967                // separate the AudioGroup and connection from the original call
968                Phone originalPhone = getPhone();
969                AudioGroup audioGroup = call.getAudioGroup(); // may be null
970                call.add(this);
971                mSipAudioCall.setAudioGroup(audioGroup);
972
973                // put the original call to bg; and the separated call becomes
974                // fg if it was in bg
975                originalPhone.switchHoldingAndActive();
976
977                // start audio and notify the phone app of the state change
978                call = (SipCall) getForegroundCall();
979                mSipAudioCall.startAudio();
980                call.onConnectionStateChanged(this);
981            }
982        }
983
984        private void log(String s) {
985            Rlog.d(SCN_TAG, s);
986        }
987    }
988
989    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
990        private static final String SACA_TAG = "SipAudioCallAdapter";
991        private static final boolean SACA_DBG = true;
992        /** Call ended with cause defined in {@link DisconnectCause}. */
993        protected abstract void onCallEnded(int cause);
994        /** Call failed with cause defined in {@link DisconnectCause}. */
995        protected abstract void onError(int cause);
996
997        @Override
998        public void onCallEnded(SipAudioCall call) {
999            if (SACA_DBG) log("onCallEnded: call=" + call);
1000            onCallEnded(call.isInCall()
1001                    ? DisconnectCause.NORMAL
1002                    : DisconnectCause.INCOMING_MISSED);
1003        }
1004
1005        @Override
1006        public void onCallBusy(SipAudioCall call) {
1007            if (SACA_DBG) log("onCallBusy: call=" + call);
1008            onCallEnded(DisconnectCause.BUSY);
1009        }
1010
1011        @Override
1012        public void onError(SipAudioCall call, int errorCode,
1013                String errorMessage) {
1014            if (SACA_DBG) {
1015                log("onError: call=" + call + " code="+ SipErrorCode.toString(errorCode)
1016                    + ": " + errorMessage);
1017            }
1018            switch (errorCode) {
1019                case SipErrorCode.SERVER_UNREACHABLE:
1020                    onError(DisconnectCause.SERVER_UNREACHABLE);
1021                    break;
1022                case SipErrorCode.PEER_NOT_REACHABLE:
1023                    onError(DisconnectCause.NUMBER_UNREACHABLE);
1024                    break;
1025                case SipErrorCode.INVALID_REMOTE_URI:
1026                    onError(DisconnectCause.INVALID_NUMBER);
1027                    break;
1028                case SipErrorCode.TIME_OUT:
1029                case SipErrorCode.TRANSACTION_TERMINTED:
1030                    onError(DisconnectCause.TIMED_OUT);
1031                    break;
1032                case SipErrorCode.DATA_CONNECTION_LOST:
1033                    onError(DisconnectCause.LOST_SIGNAL);
1034                    break;
1035                case SipErrorCode.INVALID_CREDENTIALS:
1036                    onError(DisconnectCause.INVALID_CREDENTIALS);
1037                    break;
1038                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
1039                    onError(DisconnectCause.OUT_OF_NETWORK);
1040                    break;
1041                case SipErrorCode.SERVER_ERROR:
1042                    onError(DisconnectCause.SERVER_ERROR);
1043                    break;
1044                case SipErrorCode.SOCKET_ERROR:
1045                case SipErrorCode.CLIENT_ERROR:
1046                default:
1047                    onError(DisconnectCause.ERROR_UNSPECIFIED);
1048            }
1049        }
1050
1051        private void log(String s) {
1052            Rlog.d(SACA_TAG, s);
1053        }
1054    }
1055}
1056