1ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang/*
2ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * Copyright (C) 2010 The Android Open Source Project
3ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang *
4ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * Licensed under the Apache License, Version 2.0 (the "License");
5ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * you may not use this file except in compliance with the License.
6ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * You may obtain a copy of the License at
7ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang *
8ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang *      http://www.apache.org/licenses/LICENSE-2.0
9ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang *
10ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * Unless required by applicable law or agreed to in writing, software
11ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * distributed under the License is distributed on an "AS IS" BASIS,
12ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * See the License for the specific language governing permissions and
14ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * limitations under the License.
15ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang */
16ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
17ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangpackage com.android.internal.telephony.sip;
18ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
19ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.content.Context;
201d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyanimport android.media.AudioManager;
21ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.net.rtp.AudioGroup;
22ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.net.sip.SipAudioCall;
23903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyanimport android.net.sip.SipErrorCode;
2425b52a2f97df112c2836972d0b6d9a4c7a9c4a4eHung-ying Tyanimport android.net.sip.SipException;
25ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.net.sip.SipManager;
26ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.net.sip.SipProfile;
2784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyanimport android.net.sip.SipSession;
28ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.os.AsyncResult;
29ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.os.Message;
30ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.telephony.PhoneNumberUtils;
31ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.telephony.ServiceState;
32538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyanimport android.text.TextUtils;
33ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport android.util.Log;
34ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
35ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport com.android.internal.telephony.Call;
36ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport com.android.internal.telephony.CallStateException;
37ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport com.android.internal.telephony.Connection;
38ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport com.android.internal.telephony.Phone;
39ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport com.android.internal.telephony.PhoneNotifier;
40ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
41ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport java.text.ParseException;
42ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangimport java.util.List;
43b5c72ead014a509c0f84884d1f2dac1ff9deec8eMagnus Strandbergimport java.util.regex.Pattern;
44ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
45ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang/**
46ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang * {@hide}
47ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang */
48ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wangpublic class SipPhone extends SipPhoneBase {
49ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private static final String LOG_TAG = "SipPhone";
50aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan    private static final boolean DEBUG = true;
51194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan    private static final int TIMEOUT_MAKE_CALL = 15; // in seconds
52194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan    private static final int TIMEOUT_ANSWER_CALL = 8; // in seconds
53194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan    private static final int TIMEOUT_HOLD_CALL = 15; // in seconds
54ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
55ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    // A call that is ringing or (call) waiting
56ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private SipCall ringingCall = new SipCall();
57ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private SipCall foregroundCall = new SipCall();
58ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private SipCall backgroundCall = new SipCall();
59ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
60ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private SipManager mSipManager;
61ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private SipProfile mProfile;
62ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
63ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    SipPhone (Context context, PhoneNotifier notifier, SipProfile profile) {
64ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        super(context, notifier);
65ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
66aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan        if (DEBUG) Log.d(LOG_TAG, "new SipPhone: " + profile.getUriString());
67ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        ringingCall = new SipCall();
68ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        foregroundCall = new SipCall();
69ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        backgroundCall = new SipCall();
70ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        mProfile = profile;
7184a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan        mSipManager = SipManager.newInstance(context);
72ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
73ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
7488e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang    @Override
7588e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang    public boolean equals(Object o) {
7688e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang        if (o == this) return true;
7788e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang        if (!(o instanceof SipPhone)) return false;
7888e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang        SipPhone that = (SipPhone) o;
7988e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang        return mProfile.getUriString().equals(that.mProfile.getUriString());
8088e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang    }
8188e590fb370d80d863417aae9330c8c2218f3175Chung-yih Wang
82ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public String getPhoneName() {
833294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        return "SIP:" + getUriString(mProfile);
84ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
85ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
86f57324cf4f82947296f4d1acb9df1f3c9c03134eChung-yih Wang    public String getSipUri() {
87f57324cf4f82947296f4d1acb9df1f3c9c03134eChung-yih Wang        return mProfile.getUriString();
88f57324cf4f82947296f4d1acb9df1f3c9c03134eChung-yih Wang    }
89f57324cf4f82947296f4d1acb9df1f3c9c03134eChung-yih Wang
90b12baad9357c6e6aec1f7d84fd041c54fe963407Chung-yih Wang    public boolean equals(SipPhone phone) {
91b6264a8795ed9469c80727123e3cafda1b07eda3Chung-yih Wang        return getSipUri().equals(phone.getSipUri());
92b12baad9357c6e6aec1f7d84fd041c54fe963407Chung-yih Wang    }
93b12baad9357c6e6aec1f7d84fd041c54fe963407Chung-yih Wang
94ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public boolean canTake(Object incomingCall) {
95ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        synchronized (SipPhone.class) {
96ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (!(incomingCall instanceof SipAudioCall)) return false;
97ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (ringingCall.getState().isAlive()) return false;
98ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
99ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            // FIXME: is it true that we cannot take any incoming call if
100ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            // both foreground and background are active
101ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (foregroundCall.getState().isAlive()
102ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    && backgroundCall.getState().isAlive()) {
103ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                return false;
104ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
105ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
1060e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan            try {
1070e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                SipAudioCall sipAudioCall = (SipAudioCall) incomingCall;
108aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "+++ taking call from: "
1090e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                        + sipAudioCall.getPeerProfile().getUriString());
1100e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                String localUri = sipAudioCall.getLocalProfile().getUriString();
1110e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                if (localUri.equals(mProfile.getUriString())) {
1120e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                    boolean makeCallWait = foregroundCall.getState().isAlive();
1130e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                    ringingCall.initIncomingCall(sipAudioCall, makeCallWait);
1140e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                    if (sipAudioCall.getState()
1150e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                            != SipSession.State.INCOMING_CALL) {
1160e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                        // Peer cancelled the call!
117aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                        if (DEBUG) Log.d(LOG_TAG, "    call cancelled !!");
1180e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                        ringingCall.reset();
1190e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                    }
1200e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                    return true;
1210e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                }
1220e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan            } catch (Exception e) {
1230e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                // Peer may cancel the call at any time during the time we hook
1240e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                // up ringingCall with sipAudioCall. Clean up ringingCall when
1250e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                // that happens.
1260e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan                ringingCall.reset();
127ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
128ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return false;
129ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
130ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
131ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
132ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void acceptCall() throws CallStateException {
133ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        synchronized (SipPhone.class) {
1349779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang            if ((ringingCall.getState() == Call.State.INCOMING) ||
1359779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                    (ringingCall.getState() == Call.State.WAITING)) {
136aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "acceptCall");
137ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                // Always unmute when answering a new call
1381d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                ringingCall.setMute(false);
1399779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                ringingCall.acceptCall();
140ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            } else {
141ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new CallStateException("phone not ringing");
142ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
143ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
144ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
145ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
146ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void rejectCall() throws CallStateException {
147ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        synchronized (SipPhone.class) {
148ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (ringingCall.getState().isRinging()) {
149aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "rejectCall");
150ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                ringingCall.rejectCall();
151ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            } else {
152ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new CallStateException("phone not ringing");
153ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
154ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
155ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
156ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
157ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public Connection dial(String dialString) throws CallStateException {
158ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        synchronized (SipPhone.class) {
159ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return dialInternal(dialString);
160ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
161ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
162ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
163ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private Connection dialInternal(String dialString)
164ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            throws CallStateException {
165ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        clearDisconnected();
166ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
167ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (!canDial()) {
168ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            throw new CallStateException("cannot dial in current state");
169ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
170ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (foregroundCall.getState() == SipCall.State.ACTIVE) {
171ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            switchHoldingAndActive();
172ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
173ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (foregroundCall.getState() != SipCall.State.IDLE) {
174ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            //we should have failed in !canDial() above before we get here
175ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            throw new CallStateException("cannot dial in current state");
176ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
177ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
1781d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        foregroundCall.setMute(false);
179ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        try {
180ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            Connection c = foregroundCall.dial(dialString);
181ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return c;
182ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        } catch (SipException e) {
183ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            Log.e(LOG_TAG, "dial()", e);
184ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            throw new CallStateException("dial error: " + e);
185ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
186ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
187ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
188ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void switchHoldingAndActive() throws CallStateException {
189aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan        if (DEBUG) Log.d(LOG_TAG, " ~~~~~~  switch fg and bg");
190ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        synchronized (SipPhone.class) {
191ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            foregroundCall.switchWith(backgroundCall);
192ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (backgroundCall.getState().isAlive()) backgroundCall.hold();
193ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (foregroundCall.getState().isAlive()) foregroundCall.unhold();
194ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
195ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
196ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
197ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public boolean canConference() {
198ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return true;
199ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
200ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
201ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void conference() throws CallStateException {
2023294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        synchronized (SipPhone.class) {
2033294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            if ((foregroundCall.getState() != SipCall.State.ACTIVE)
2043294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                    || (foregroundCall.getState() != SipCall.State.ACTIVE)) {
2053294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                throw new CallStateException("wrong state to merge calls: fg="
2063294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                        + foregroundCall.getState() + ", bg="
2073294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                        + backgroundCall.getState());
2083294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            }
2093294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            foregroundCall.merge(backgroundCall);
2108eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan        }
2118eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan    }
2128eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan
2138eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan    public void conference(Call that) throws CallStateException {
2143294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        synchronized (SipPhone.class) {
2153294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            if (!(that instanceof SipCall)) {
2163294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                throw new CallStateException("expect " + SipCall.class
2173294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                        + ", cannot merge with " + that.getClass());
2183294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            }
2193294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            foregroundCall.merge((SipCall) that);
2208eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan        }
221ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
222ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
223ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public boolean canTransfer() {
224ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return false;
225ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
226ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
227ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void explicitCallTransfer() throws CallStateException {
228ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        //mCT.explicitCallTransfer();
229ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
230ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
231ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void clearDisconnected() {
2323294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        synchronized (SipPhone.class) {
2333294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            ringingCall.clearDisconnected();
2343294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            foregroundCall.clearDisconnected();
2353294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            backgroundCall.clearDisconnected();
236ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
2373294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            updatePhoneState();
2383294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            notifyPreciseCallStateChanged();
2393294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        }
240ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
241ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
242ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void sendDtmf(char c) {
243ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (!PhoneNumberUtils.is12Key(c)) {
244ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            Log.e(LOG_TAG,
245ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    "sendDtmf called with invalid character '" + c + "'");
246ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        } else if (foregroundCall.getState().isAlive()) {
2473294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            synchronized (SipPhone.class) {
2483294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                foregroundCall.sendDtmf(c);
2493294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            }
250ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
251ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
252ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
253ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void startDtmf(char c) {
254ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (!PhoneNumberUtils.is12Key(c)) {
255ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            Log.e(LOG_TAG,
256ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                "startDtmf called with invalid character '" + c + "'");
257ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        } else {
258ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            sendDtmf(c);
259ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
260ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
261ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
262ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void stopDtmf() {
263ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // no op
264ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
265ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
266ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void sendBurstDtmf(String dtmfString) {
267ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        Log.e(LOG_TAG, "[SipPhone] sendBurstDtmf() is a CDMA method");
268ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
269ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
270ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void getOutgoingCallerIdDisplay(Message onComplete) {
271ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // FIXME: what to reply?
272ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        AsyncResult.forMessage(onComplete, null, null);
273ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        onComplete.sendToTarget();
274ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
275ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
276ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void setOutgoingCallerIdDisplay(int commandInterfaceCLIRMode,
277ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                                           Message onComplete) {
278ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // FIXME: what's this for SIP?
279ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        AsyncResult.forMessage(onComplete, null, null);
280ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        onComplete.sendToTarget();
281ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
282ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
283ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void getCallWaiting(Message onComplete) {
284ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // FIXME: what to reply?
285ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        AsyncResult.forMessage(onComplete, null, null);
286ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        onComplete.sendToTarget();
287ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
288ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
289ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void setCallWaiting(boolean enable, Message onComplete) {
290ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // FIXME: what to reply?
291ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        Log.e(LOG_TAG, "call waiting not supported");
292ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
293ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
29423f21600d0927365e5e7bdc4e566ba52101301b4Hung-ying Tyan    @Override
29523f21600d0927365e5e7bdc4e566ba52101301b4Hung-ying Tyan    public void setEchoSuppressionEnabled(boolean enabled) {
2961d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        // TODO: Remove the enabled argument. We should check the speakerphone
2971d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        // state with AudioManager instead of keeping a state here so the
2981d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        // method with a state argument is redundant. Also rename the method
2991d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        // to something like onSpeaerphoneStateChanged(). Echo suppression may
3001d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        // not be available on every device.
30123f21600d0927365e5e7bdc4e566ba52101301b4Hung-ying Tyan        synchronized (SipPhone.class) {
3021d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            foregroundCall.setAudioGroupMode();
30323f21600d0927365e5e7bdc4e566ba52101301b4Hung-ying Tyan        }
30423f21600d0927365e5e7bdc4e566ba52101301b4Hung-ying Tyan    }
30523f21600d0927365e5e7bdc4e566ba52101301b4Hung-ying Tyan
306ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public void setMute(boolean muted) {
3073294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        synchronized (SipPhone.class) {
3083294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            foregroundCall.setMute(muted);
3093294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        }
310ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
311ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
312ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public boolean getMute() {
31365a7f147deb02f728959eb05913a2d6ce53dea1cHung-ying Tyan        return (foregroundCall.getState().isAlive()
31465a7f147deb02f728959eb05913a2d6ce53dea1cHung-ying Tyan                ? foregroundCall.getMute()
31565a7f147deb02f728959eb05913a2d6ce53dea1cHung-ying Tyan                : backgroundCall.getMute());
316ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
317ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
318ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public Call getForegroundCall() {
319ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return foregroundCall;
320ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
321ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
322ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public Call getBackgroundCall() {
323ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return backgroundCall;
324ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
325ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
326ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public Call getRingingCall() {
327ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return ringingCall;
328ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
329ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
330ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    public ServiceState getServiceState() {
331ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // FIXME: we may need to provide this when data connectivity is lost
332ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // or when server is down
333ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return super.getServiceState();
334ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
335ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
336ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private String getUriString(SipProfile p) {
337ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // SipProfile.getUriString() may contain "SIP:" and port
338ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        return p.getUserName() + "@" + getSipDomain(p);
339ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
340ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
341ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private String getSipDomain(SipProfile p) {
342ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        String domain = p.getSipDomain();
343ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        // TODO: move this to SipProfile
344ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (domain.endsWith(":5060")) {
345ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return domain.substring(0, domain.length() - 5);
346ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        } else {
347ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return domain;
348ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
349ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
350ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
351ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private class SipCall extends SipCallBase {
3520e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan        void reset() {
3530e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan            connections.clear();
3540e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan            setState(Call.State.IDLE);
3550e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan        }
3560e412304813ccd3a3fb6a643836e4f0922d1dc44Hung-ying Tyan
357ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void switchWith(SipCall that) {
358ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            synchronized (SipPhone.class) {
359ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                SipCall tmp = new SipCall();
360ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                tmp.takeOver(this);
361ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                this.takeOver(that);
362ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                that.takeOver(tmp);
363ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
364ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
365ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
366ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private void takeOver(SipCall that) {
367ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            connections = that.connections;
368ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            state = that.state;
369ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            for (Connection c : connections) {
370ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                ((SipConnection) c).changeOwner(this);
371ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
372ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
373ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
374ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
375ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public Phone getPhone() {
376ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return SipPhone.this;
377ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
378ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
379ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
380ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public List<Connection> getConnections() {
381ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            synchronized (SipPhone.class) {
382ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                // FIXME should return Collections.unmodifiableList();
383ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                return connections;
384ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
385ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
386ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
3871d1583573d2099756bbbeef48d97c280edc393e0Hung-ying Tyan        Connection dial(String originalNumber) throws SipException {
3881d1583573d2099756bbbeef48d97c280edc393e0Hung-ying Tyan            String calleeSipUri = originalNumber;
389ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (!calleeSipUri.contains("@")) {
390b5c72ead014a509c0f84884d1f2dac1ff9deec8eMagnus Strandberg                String replaceStr = Pattern.quote(mProfile.getUserName() + "@");
391b5c72ead014a509c0f84884d1f2dac1ff9deec8eMagnus Strandberg                calleeSipUri = mProfile.getUriString().replaceFirst(replaceStr,
392f053292d7a46c30abbe6f12ca04dbc03ec964d80Chung-yih Wang                        calleeSipUri + "@");
393ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
394ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            try {
395ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                SipProfile callee =
396ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        new SipProfile.Builder(calleeSipUri).build();
397f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan                SipConnection c = new SipConnection(this, callee,
398f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan                        originalNumber);
399ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                c.dial();
4008d1b2a17d9935819ec96f1b5fca0e9945f564eaaHung-ying Tyan                connections.add(c);
401ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                setState(Call.State.DIALING);
402ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                return c;
403ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            } catch (ParseException e) {
404ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new SipException("dial", e);
405ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
406ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
407ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
408ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
409ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public void hangup() throws CallStateException {
4103294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            synchronized (SipPhone.class) {
411c508e8468f54164e7c9729e3800e2559b803cef1Hung-ying Tyan                if (state.isAlive()) {
412aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                    if (DEBUG) Log.d(LOG_TAG, "hang up call: " + getState()
413aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                            + ": " + this + " on phone " + getPhone());
414421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    setState(State.DISCONNECTING);
4152b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                    CallStateException excp = null;
4162b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                    for (Connection c : connections) {
4172b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                        try {
4182b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                            c.hangup();
4192b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                        } catch (CallStateException e) {
4202b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                            excp = e;
4212b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                        }
4223294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                    }
4232b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                    if (excp != null) throw excp;
4242b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                } else {
425aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                    if (DEBUG) Log.d(LOG_TAG, "hang up dead call: " + getState()
426aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                            + ": " + this + " on phone " + getPhone());
427ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
428ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
429ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
430ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
431ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void initIncomingCall(SipAudioCall sipAudioCall, boolean makeCallWait) {
432ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            SipProfile callee = sipAudioCall.getPeerProfile();
433d07833f54b6e8e361b666ae16efa15fdf60159deDavid Brown            SipConnection c = new SipConnection(this, callee);
434ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            connections.add(c);
435ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
436ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            Call.State newState = makeCallWait ? State.WAITING : State.INCOMING;
437ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            c.initIncomingCall(sipAudioCall, newState);
438ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
439ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            setState(newState);
440ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            notifyNewRingingConnectionP(c);
441ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
442ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
443ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void rejectCall() throws CallStateException {
444ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            hangup();
445ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
446ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
447ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void acceptCall() throws CallStateException {
4489779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang            if (this != ringingCall) {
4499779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                throw new CallStateException("acceptCall() in a non-ringing call");
450ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
451ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (connections.size() != 1) {
452ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new CallStateException("acceptCall() in a conf call");
453ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
454ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            ((SipConnection) connections.get(0)).acceptCall();
455ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
456ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
4571d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        private boolean isSpeakerOn() {
4581d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            return ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
4591d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                    .isSpeakerphoneOn();
4601d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        }
4611d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan
4621d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        void setAudioGroupMode() {
4633294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            AudioGroup audioGroup = getAudioGroup();
4641d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            if (audioGroup == null) return;
4651d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            int mode = audioGroup.getMode();
4661d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            if (state == State.HOLDING) {
4673294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
4681d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            } else if (getMute()) {
4691d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                audioGroup.setMode(AudioGroup.MODE_MUTED);
4701d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            } else if (isSpeakerOn()) {
4711d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                audioGroup.setMode(AudioGroup.MODE_ECHO_SUPPRESSION);
4721d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            } else {
4731d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                audioGroup.setMode(AudioGroup.MODE_NORMAL);
4743294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            }
4751d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            if (DEBUG) Log.d(LOG_TAG, String.format(
4761d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                    "audioGroup mode change: %d --> %d", mode,
4771d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                    audioGroup.getMode()));
4781d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        }
4791d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan
4801d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        void hold() throws CallStateException {
4811d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            setState(State.HOLDING);
482ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            for (Connection c : connections) ((SipConnection) c).hold();
4831d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            setAudioGroupMode();
484ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
485ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
486ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void unhold() throws CallStateException {
487ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            setState(State.ACTIVE);
4883294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            AudioGroup audioGroup = new AudioGroup();
4893294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            for (Connection c : connections) {
4903294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                ((SipConnection) c).unhold(audioGroup);
4913294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            }
4921d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            setAudioGroupMode();
493ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
494ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
495ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void setMute(boolean muted) {
4961d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            for (Connection c : connections) {
4971d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                ((SipConnection) c).setMute(muted);
4981d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            }
499ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
500ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
501ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        boolean getMute() {
5021d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            return connections.isEmpty()
5031d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                    ? false
5041d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                    : ((SipConnection) connections.get(0)).getMute();
505ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
506ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
5078eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan        void merge(SipCall that) throws CallStateException {
5083294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            AudioGroup audioGroup = getAudioGroup();
5096037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan
5106037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan            // copy to an array to avoid concurrent modification as connections
5116037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan            // in that.connections will be removed in add(SipConnection).
5126037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan            Connection[] cc = that.connections.toArray(
5136037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                    new Connection[that.connections.size()]);
5146037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan            for (Connection c : cc) {
5158eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan                SipConnection conn = (SipConnection) c;
5163294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                add(conn);
5173294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                if (conn.getState() == Call.State.HOLDING) {
5183294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                    conn.unhold(audioGroup);
5193294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                }
5208eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan            }
5218eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan            that.setState(Call.State.IDLE);
5228eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan        }
5238eac20eacd088793547c56e14d602b28d62fb278Hung-ying Tyan
5243294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        private void add(SipConnection conn) {
5253294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            SipCall call = conn.getCall();
5263294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            if (call == this) return;
5273294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            if (call != null) call.connections.remove(conn);
5283294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan
5293294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            connections.add(conn);
5303294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            conn.changeOwner(this);
5313294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        }
5323294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan
533ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void sendDtmf(char c) {
534ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            AudioGroup audioGroup = getAudioGroup();
535ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (audioGroup == null) return;
536ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            audioGroup.sendDtmf(convertDtmf(c));
537ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
538ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
539ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private int convertDtmf(char c) {
540ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            int code = c - '0';
541ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if ((code < 0) || (code > 9)) {
542ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                switch (c) {
543ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    case '*': return 10;
544ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    case '#': return 11;
545ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    case 'A': return 12;
546ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    case 'B': return 13;
547ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    case 'C': return 14;
548ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    case 'D': return 15;
549ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    default:
550ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        throw new IllegalArgumentException(
551ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                                "invalid DTMF char: " + (int) c);
552ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
553ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
554ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return code;
555ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
556ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
557ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
558ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        protected void setState(State newState) {
559ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (state != newState) {
560aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.v(LOG_TAG, "+***+ call state changed: " + state
561ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        + " --> " + newState + ": " + this + ": on phone "
562ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        + getPhone() + " " + connections.size());
563ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
564ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                if (newState == Call.State.ALERTING) {
565ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    state = newState; // need in ALERTING to enable ringback
566ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    SipPhone.this.startRingbackTone();
567ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                } else if (state == Call.State.ALERTING) {
568ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    SipPhone.this.stopRingbackTone();
569ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
570ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                state = newState;
571ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                updatePhoneState();
572ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                notifyPreciseCallStateChanged();
573ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
574ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
575ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
576ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void onConnectionStateChanged(SipConnection conn) {
577ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            // this can be called back when a conf call is formed
578ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (state != State.ACTIVE) {
579ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                setState(conn.getState());
580ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
581ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
582ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
583ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void onConnectionEnded(SipConnection conn) {
584ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            // set state to DISCONNECTED only when all conns are disconnected
585ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (state != State.DISCONNECTED) {
586ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                boolean allConnectionsDisconnected = true;
587aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "---check connections: "
588025a39af346f39743c1e384b9000ce1baee36562Hung-ying Tyan                        + connections.size());
589ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                for (Connection c : connections) {
590aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                    if (DEBUG) Log.d(LOG_TAG, "   state=" + c.getState() + ": "
591aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                            + c);
592ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    if (c.getState() != State.DISCONNECTED) {
593ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        allConnectionsDisconnected = false;
594ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        break;
595ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    }
596ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
597ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                if (allConnectionsDisconnected) setState(State.DISCONNECTED);
598ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
599ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            notifyDisconnectP(conn);
600ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
601ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
602ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private AudioGroup getAudioGroup() {
603ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (connections.isEmpty()) return null;
604ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return ((SipConnection) connections.get(0)).getAudioGroup();
605ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
606ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
607ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
608ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private class SipConnection extends SipConnectionBase {
609ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private SipCall mOwner;
610ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private SipAudioCall mSipAudioCall;
611ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private Call.State mState = Call.State.IDLE;
612ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private SipProfile mPeer;
613f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan        private String mOriginalNumber; // may be a PSTN number
614ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private boolean mIncoming = false;
615ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
616ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        private SipAudioCallAdapter mAdapter = new SipAudioCallAdapter() {
617ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            @Override
618ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            protected void onCallEnded(DisconnectCause cause) {
619ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                if (getDisconnectCause() != DisconnectCause.LOCAL) {
620ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    setDisconnectCause(cause);
621ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
622ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                synchronized (SipPhone.class) {
623ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    setState(Call.State.DISCONNECTED);
624421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    SipAudioCall sipAudioCall = mSipAudioCall;
625421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    mSipAudioCall = null;
626421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    String sessionState = (sipAudioCall == null)
627421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                            ? ""
628421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                            : (sipAudioCall.getState() + ", ");
629aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                    if (DEBUG) Log.d(LOG_TAG, "--- connection ended: "
630421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                            + mPeer.getUriString() + ": " + sessionState
631421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                            + "cause: " + getDisconnectCause() + ", on phone "
632ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                            + getPhone());
633421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    if (sipAudioCall != null) {
634421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                        sipAudioCall.setListener(null);
635421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                        sipAudioCall.close();
636421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    }
637421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    mOwner.onConnectionEnded(SipConnection.this);
638ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
639ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
640ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
641ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            @Override
642bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang            public void onCallEstablished(SipAudioCall call) {
643bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang                onChanged(call);
644245475925eff61ee76bde58de69253a889e39d0aChung-yih Wang                if (mState == Call.State.ACTIVE) call.startAudio();
645bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang            }
646bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang
647bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang            @Override
648bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang            public void onCallHeld(SipAudioCall call) {
649bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang                onChanged(call);
650245475925eff61ee76bde58de69253a889e39d0aChung-yih Wang                if (mState == Call.State.HOLDING) call.startAudio();
651bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang            }
652bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang
653bd2294204e3edaede3fe81eb9b11c05c4fafe627Chung-yih Wang            @Override
654ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            public void onChanged(SipAudioCall call) {
655ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                synchronized (SipPhone.class) {
656ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    Call.State newState = getCallStateFrom(call);
657ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    if (mState == newState) return;
658ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    if (newState == Call.State.INCOMING) {
659ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        setState(mOwner.getState()); // INCOMING or WAITING
660ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    } else {
6619779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                        if (mOwner == ringingCall) {
6629779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                            if (ringingCall.getState() == Call.State.WAITING) {
6639779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                try {
6649779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                    switchHoldingAndActive();
6659779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                } catch (CallStateException e) {
6669779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                    // disconnect the call.
6679779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                    onCallEnded(DisconnectCause.LOCAL);
6689779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                    return;
6699779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                                }
6709779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                            }
6719779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                            foregroundCall.switchWith(ringingCall);
6729779b714f4035642b87cbb7ef6cd8ac32848c930Chung-yih Wang                        }
673ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                        setState(newState);
674ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    }
675ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                    mOwner.onConnectionStateChanged(SipConnection.this);
676aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                    if (DEBUG) Log.v(LOG_TAG, "+***+ connection state changed: "
677ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                            + mPeer.getUriString() + ": " + mState
678ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                            + " on phone " + getPhone());
679ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                }
680ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
681ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
682ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            @Override
683903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            protected void onError(DisconnectCause cause) {
684aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "SIP error: " + cause);
685903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                onCallEnded(cause);
686ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
687ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        };
688ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
689f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan        public SipConnection(SipCall owner, SipProfile callee,
690f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan                String originalNumber) {
691f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            super(originalNumber);
692ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mOwner = owner;
693ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mPeer = callee;
694f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            mOriginalNumber = originalNumber;
695f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan        }
696f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan
697f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan        public SipConnection(SipCall owner, SipProfile callee) {
698f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            this(owner, callee, getUriString(callee));
699ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
700ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
701538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan        @Override
702538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan        public String getCnapName() {
703538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan            String displayName = mPeer.getDisplayName();
704538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan            return TextUtils.isEmpty(displayName) ? null
705538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan                                                  : displayName;
706538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan        }
707538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan
708538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan        @Override
709538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan        public int getNumberPresentation() {
710538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan            return Connection.PRESENTATION_ALLOWED;
711538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan        }
712538e58fc757b0d10672235bc17b1380854845139Hung-ying Tyan
713ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void initIncomingCall(SipAudioCall sipAudioCall, Call.State newState) {
714ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            setState(newState);
715ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mSipAudioCall = sipAudioCall;
716ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            sipAudioCall.setListener(mAdapter); // call back to set state
717ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mIncoming = true;
718ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
719ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
720ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void acceptCall() throws CallStateException {
721ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            try {
722194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan                mSipAudioCall.answerCall(TIMEOUT_ANSWER_CALL);
723ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            } catch (SipException e) {
724ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new CallStateException("acceptCall(): " + e);
725ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
726ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
727ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
728ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void changeOwner(SipCall owner) {
729ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mOwner = owner;
730ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
731ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
732ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        AudioGroup getAudioGroup() {
733ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (mSipAudioCall == null) return null;
734ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return mSipAudioCall.getAudioGroup();
735ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
736ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
737ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void dial() throws SipException {
738ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            setState(Call.State.DIALING);
73984a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
740194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan                    TIMEOUT_MAKE_CALL);
741ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mSipAudioCall.setListener(mAdapter);
742ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
743ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
744ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        void hold() throws CallStateException {
7453294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            setState(Call.State.HOLDING);
746ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            try {
747194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan                mSipAudioCall.holdCall(TIMEOUT_HOLD_CALL);
748ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            } catch (SipException e) {
749ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new CallStateException("hold(): " + e);
750ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
751ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
752ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
7533294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan        void unhold(AudioGroup audioGroup) throws CallStateException {
7543294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            mSipAudioCall.setAudioGroup(audioGroup);
7553294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            setState(Call.State.ACTIVE);
756ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            try {
757194bbcce9ba15634500f542b9ea017b2cf154b45Hung-ying Tyan                mSipAudioCall.continueCall(TIMEOUT_HOLD_CALL);
758ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            } catch (SipException e) {
759ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                throw new CallStateException("unhold(): " + e);
760ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
761ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
762ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
7631d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        void setMute(boolean muted) {
7641d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            if ((mSipAudioCall != null) && (muted != mSipAudioCall.isMuted())) {
7651d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                mSipAudioCall.toggleMute();
7661d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            }
7671d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        }
7681d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan
7691d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        boolean getMute() {
7701d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan            return (mSipAudioCall == null) ? false
7711d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan                                           : mSipAudioCall.isMuted();
7721d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan        }
7731d12ef09a8e6ebc6638f4ff2f561c50c950023cbHung-ying Tyan
774ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
775ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        protected void setState(Call.State state) {
776ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            if (state == mState) return;
777ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            super.setState(state);
778ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            mState = state;
779ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
780ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
781ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
782ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public Call.State getState() {
783ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return mState;
784ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
785ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
786ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
787ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public boolean isIncoming() {
788ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return mIncoming;
789ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
790ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
791ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
792ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public String getAddress() {
793f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            // Phone app uses this to query caller ID. Return the original dial
794f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            // number (which may be a PSTN number) instead of the peer's SIP
795f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            // URI.
796f5201ab71ff4d104265ab126e86afc6b81da8011Hung-ying Tyan            return mOriginalNumber;
797ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
798ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
799ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
800ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public SipCall getCall() {
801ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return mOwner;
802ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
803ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
804ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
805ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        protected Phone getPhone() {
806ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            return mOwner.getPhone();
807ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
808ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
809ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
810ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public void hangup() throws CallStateException {
8113294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            synchronized (SipPhone.class) {
812aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "hangup conn: " + mPeer.getUriString()
813aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                        + ": " + mState + ": on phone "
814aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                        + getPhone().getPhoneName());
815421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                if (!mState.isAlive()) return;
8163294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                try {
817421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    SipAudioCall sipAudioCall = mSipAudioCall;
818421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                    if (sipAudioCall != null) {
819421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                        sipAudioCall.setListener(null);
820421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                        sipAudioCall.endCall();
8212b4f5cfd9be5ceffc4745a45736e067a475a4dffHung-ying Tyan                    }
8223294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                } catch (SipException e) {
8233294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                    throw new CallStateException("hangup(): " + e);
824421c34c162098efe870574844a7ee49812bbb929Hung-ying Tyan                } finally {
82517956e626b38ce53da61e78af2c973ed41c9e461Hung-ying Tyan                    mAdapter.onCallEnded(((mState == Call.State.INCOMING)
82617956e626b38ce53da61e78af2c973ed41c9e461Hung-ying Tyan                            || (mState == Call.State.WAITING))
82717956e626b38ce53da61e78af2c973ed41c9e461Hung-ying Tyan                            ? DisconnectCause.INCOMING_REJECTED
82817956e626b38ce53da61e78af2c973ed41c9e461Hung-ying Tyan                            : DisconnectCause.LOCAL);
8293294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                }
830ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
831ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
832ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
833ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
834ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public void separate() throws CallStateException {
8353294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan            synchronized (SipPhone.class) {
8366037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                SipCall call = (getPhone() == SipPhone.this)
8376037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                        ? (SipCall) SipPhone.this.getBackgroundCall()
8386037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                        : (SipCall) SipPhone.this.getForegroundCall();
8393294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                if (call.getState() != Call.State.IDLE) {
8403294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                    throw new CallStateException(
8413294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                            "cannot put conn back to a call in non-idle state: "
8423294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                            + call.getState());
8433294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                }
844aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                if (DEBUG) Log.d(LOG_TAG, "separate conn: "
845aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                        + mPeer.getUriString() + " from " + mOwner + " back to "
846aeba1bc0c333f145469fc17a10c0bbcebd6dc30bHung-ying Tyan                        + call);
8473294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan
8486037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                // separate the AudioGroup and connection from the original call
8496037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                Phone originalPhone = getPhone();
8503294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                AudioGroup audioGroup = call.getAudioGroup(); // may be null
8513294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                call.add(this);
8523294d44b96f63f647fba3a03604eb028e28a42bcHung-ying Tyan                mSipAudioCall.setAudioGroup(audioGroup);
8536037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan
8546037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                // put the original call to bg; and the separated call becomes
8556037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                // fg if it was in bg
8566037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                originalPhone.switchHoldingAndActive();
8576037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan
8586037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                // start audio and notify the phone app of the state change
8596037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                call = (SipCall) SipPhone.this.getForegroundCall();
8606037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                mSipAudioCall.startAudio();
8616037a056ea0dda27a286ddcb527b323b58a1c7c7Hung-ying Tyan                call.onConnectionStateChanged(this);
862ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            }
863ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
864ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
865ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
866ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
867ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    private static Call.State getCallStateFrom(SipAudioCall sipAudioCall) {
868ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
86997963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        int sessionState = sipAudioCall.getState();
870ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        switch (sessionState) {
87184a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
87284a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.INCOMING_CALL:
87384a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
87484a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
87584a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
87684a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
87784a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
878ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            default:
879ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                Log.w(LOG_TAG, "illegal connection state: " + sessionState);
880ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang                return Call.State.DISCONNECTED;
881ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
882ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
883ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
88484a357bb6a8005e1c5e924e96a8ecf310e77c47cHung-ying Tyan    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
885ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        protected abstract void onCallEnded(Connection.DisconnectCause cause);
886903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan        protected abstract void onError(Connection.DisconnectCause cause);
887ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
888ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
889ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public void onCallEnded(SipAudioCall call) {
8908544560ccc43de7ff49d91866f461f5572f0b147Hung-ying Tyan            onCallEnded(call.isInCall()
8918544560ccc43de7ff49d91866f461f5572f0b147Hung-ying Tyan                    ? Connection.DisconnectCause.NORMAL
8928544560ccc43de7ff49d91866f461f5572f0b147Hung-ying Tyan                    : Connection.DisconnectCause.INCOMING_MISSED);
893ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
894ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
895ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
896ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        public void onCallBusy(SipAudioCall call) {
897ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang            onCallEnded(Connection.DisconnectCause.BUSY);
898ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
899ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang
900ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        @Override
90197963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan        public void onError(SipAudioCall call, int errorCode,
902903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                String errorMessage) {
90313f6270eb14b409709c936b828e2a2fd40e427c4Hung-ying Tyan            switch (errorCode) {
904c6548fd9eda7b58f5a2e2a9c01e3c7cafd42fafbHung-ying Tyan                case SipErrorCode.SERVER_UNREACHABLE:
905c6548fd9eda7b58f5a2e2a9c01e3c7cafd42fafbHung-ying Tyan                    onError(Connection.DisconnectCause.SERVER_UNREACHABLE);
906c6548fd9eda7b58f5a2e2a9c01e3c7cafd42fafbHung-ying Tyan                    break;
90797963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.PEER_NOT_REACHABLE:
908ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                    onError(Connection.DisconnectCause.NUMBER_UNREACHABLE);
909ae076d3981fda732d54b6c6e37e5659b2e7ba130Hung-ying Tyan                    break;
91097963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.INVALID_REMOTE_URI:
911903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    onError(Connection.DisconnectCause.INVALID_NUMBER);
912903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    break;
91397963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.TIME_OUT:
91497963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.TRANSACTION_TERMINTED:
9153d7606aa607b24817e37c264f2141ed7b2d50be0Hung-ying Tyan                    onError(Connection.DisconnectCause.TIMED_OUT);
916903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    break;
91797963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.DATA_CONNECTION_LOST:
918d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan                    onError(Connection.DisconnectCause.LOST_SIGNAL);
919d231aa880ab006d51ffe03454c1fc082f1c97bb8Hung-ying Tyan                    break;
92097963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.INVALID_CREDENTIALS:
921903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    onError(Connection.DisconnectCause.INVALID_CREDENTIALS);
922903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    break;
92300a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan                case SipErrorCode.CROSS_DOMAIN_AUTHENTICATION:
92400a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan                    onError(Connection.DisconnectCause.OUT_OF_NETWORK);
92500a22064efef4f574e439079aae2deae1a087a31Hung-ying Tyan                    break;
92697963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.SERVER_ERROR:
927624d5b4e8c20516516d0bff74479b9f5abdfe61cHung-ying Tyan                    onError(Connection.DisconnectCause.SERVER_ERROR);
928624d5b4e8c20516516d0bff74479b9f5abdfe61cHung-ying Tyan                    break;
929624d5b4e8c20516516d0bff74479b9f5abdfe61cHung-ying Tyan                case SipErrorCode.SOCKET_ERROR:
93097963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                case SipErrorCode.CLIENT_ERROR:
931903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                default:
93297963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                    Log.w(LOG_TAG, "error: " + SipErrorCode.toString(errorCode)
93397963794af1e18674dd111e3ad344d90b16c922cHung-ying Tyan                            + ": " + errorMessage);
934903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan                    onError(Connection.DisconnectCause.ERROR_UNSPECIFIED);
935903e1031605d715e904811b0dd06cc6a518f0048Hung-ying Tyan            }
936ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang        }
937ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang    }
938ccd0b6953f5f77d1da5f540a3ba5ea71116e14f0Chung-yih Wang}
939