1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.services.telephony.sip;
18
19import android.net.Uri;
20import android.os.Handler;
21import android.os.Message;
22import android.telecom.AudioState;
23import android.telecom.Connection;
24import android.telecom.DisconnectCause;
25import android.telecom.PhoneAccount;
26import android.telecom.PhoneCapabilities;
27import android.util.Log;
28
29import com.android.internal.telephony.Call;
30import com.android.internal.telephony.CallStateException;
31import com.android.internal.telephony.sip.SipPhone;
32import com.android.services.telephony.DisconnectCauseUtil;
33
34import java.util.Objects;
35
36final class SipConnection extends Connection {
37    private static final String PREFIX = "[SipConnection] ";
38    private static final boolean VERBOSE = false; /* STOP SHIP if true */
39
40    private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
41
42    private final Handler mHandler = new Handler() {
43        @Override
44        public void handleMessage(Message msg) {
45            switch (msg.what) {
46                case MSG_PRECISE_CALL_STATE_CHANGED:
47                    updateState(false);
48                    break;
49            }
50        }
51    };
52
53    private com.android.internal.telephony.Connection mOriginalConnection;
54    private Call.State mOriginalConnectionState = Call.State.IDLE;
55
56    SipConnection() {
57        if (VERBOSE) log("new SipConnection");
58        setInitializing();
59    }
60
61    void initialize(com.android.internal.telephony.Connection connection) {
62        if (VERBOSE) log("init SipConnection, connection: " + connection);
63        mOriginalConnection = connection;
64        if (getPhone() != null) {
65            getPhone().registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED,
66                    null);
67        }
68        updateAddress();
69        setInitialized();
70    }
71
72    @Override
73    public void onAudioStateChanged(AudioState state) {
74        if (VERBOSE) log("onAudioStateChanged: " + state);
75        if (getPhone() != null) {
76            getPhone().setEchoSuppressionEnabled();
77        }
78    }
79
80    @Override
81    public void onStateChanged(int state) {
82        if (VERBOSE) log("onStateChanged, state: " + Connection.stateToString(state));
83    }
84
85    @Override
86    public void onPlayDtmfTone(char c) {
87        if (VERBOSE) log("onPlayDtmfTone");
88        if (getPhone() != null) {
89            getPhone().startDtmf(c);
90        }
91    }
92
93    @Override
94    public void onStopDtmfTone() {
95        if (VERBOSE) log("onStopDtmfTone");
96        if (getPhone() != null) {
97            getPhone().stopDtmf();
98        }
99    }
100
101    @Override
102    public void onDisconnect() {
103        if (VERBOSE) log("onDisconnect");
104        try {
105            if (getCall() != null && !getCall().isMultiparty()) {
106                getCall().hangup();
107            } else if (mOriginalConnection != null) {
108                mOriginalConnection.hangup();
109            }
110        } catch (CallStateException e) {
111            log("onDisconnect, exception: " + e);
112        }
113    }
114
115    @Override
116    public void onSeparate() {
117        if (VERBOSE) log("onSeparate");
118        try {
119            if (mOriginalConnection != null) {
120                mOriginalConnection.separate();
121            }
122        } catch (CallStateException e) {
123            log("onSeparate, exception: " + e);
124        }
125    }
126
127    @Override
128    public void onAbort() {
129        if (VERBOSE) log("onAbort");
130        onDisconnect();
131    }
132
133    @Override
134    public void onHold() {
135        if (VERBOSE) log("onHold");
136        try {
137            if (getPhone() != null && getState() == STATE_ACTIVE) {
138                getPhone().switchHoldingAndActive();
139            }
140        } catch (CallStateException e) {
141            log("onHold, exception: " + e);
142        }
143    }
144
145    @Override
146    public void onUnhold() {
147        if (VERBOSE) log("onUnhold");
148        try {
149            if (getPhone() != null && getState() == STATE_HOLDING) {
150                getPhone().switchHoldingAndActive();
151            }
152        } catch (CallStateException e) {
153            log("onUnhold, exception: " + e);
154        }
155    }
156
157    @Override
158    public void onAnswer(int videoState) {
159        if (VERBOSE) log("onAnswer");
160        try {
161            if (isValidRingingCall() && getPhone() != null) {
162                getPhone().acceptCall(videoState);
163            }
164        } catch (CallStateException e) {
165            log("onAnswer, exception: " + e);
166        }
167    }
168
169    @Override
170    public void onReject() {
171        if (VERBOSE) log("onReject");
172        try {
173            if (isValidRingingCall() && getPhone() != null) {
174                getPhone().rejectCall();
175            }
176        } catch (CallStateException e) {
177            log("onReject, exception: " + e);
178        }
179    }
180
181    @Override
182    public void onPostDialContinue(boolean proceed) {
183        if (VERBOSE) log("onPostDialContinue, proceed: " + proceed);
184        // SIP doesn't have post dial support.
185    }
186
187    private Call getCall() {
188        if (mOriginalConnection != null) {
189            return mOriginalConnection.getCall();
190        }
191        return null;
192    }
193
194    SipPhone getPhone() {
195        Call call = getCall();
196        if (call != null) {
197            return (SipPhone) call.getPhone();
198        }
199        return null;
200    }
201
202    private boolean isValidRingingCall() {
203        Call call = getCall();
204        return call != null && call.getState().isRinging() &&
205                call.getEarliestConnection() == mOriginalConnection;
206    }
207
208    private void updateState(boolean force) {
209        if (mOriginalConnection == null) {
210            return;
211        }
212
213        Call.State newState = mOriginalConnection.getState();
214        if (VERBOSE) log("updateState, " + mOriginalConnectionState + " -> " + newState);
215        if (force || mOriginalConnectionState != newState) {
216            mOriginalConnectionState = newState;
217            switch (newState) {
218                case IDLE:
219                    break;
220                case ACTIVE:
221                    setActive();
222                    break;
223                case HOLDING:
224                    setOnHold();
225                    break;
226                case DIALING:
227                case ALERTING:
228                    setDialing();
229                    // For SIP calls, we need to ask the framework to play the ringback for us.
230                    setRingbackRequested(true);
231                    break;
232                case INCOMING:
233                case WAITING:
234                    setRinging();
235                    break;
236                case DISCONNECTED:
237                    setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
238                            mOriginalConnection.getDisconnectCause()));
239                    close();
240                    break;
241                case DISCONNECTING:
242                    break;
243            }
244            updateCallCapabilities(force);
245        }
246    }
247
248    private int buildCallCapabilities() {
249        int capabilities = PhoneCapabilities.MUTE | PhoneCapabilities.SUPPORT_HOLD;
250        if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
251            capabilities |= PhoneCapabilities.HOLD;
252        }
253        return capabilities;
254    }
255
256    void updateCallCapabilities(boolean force) {
257        int newCallCapabilities = buildCallCapabilities();
258        if (force || getCallCapabilities() != newCallCapabilities) {
259            setCallCapabilities(newCallCapabilities);
260        }
261    }
262
263    void onAddedToCallService() {
264        if (VERBOSE) log("onAddedToCallService");
265        updateState(true);
266        updateCallCapabilities(true);
267        setAudioModeIsVoip(true);
268        if (mOriginalConnection != null) {
269            setCallerDisplayName(mOriginalConnection.getCnapName(),
270                    mOriginalConnection.getCnapNamePresentation());
271        }
272    }
273
274    /**
275     * Updates the handle on this connection based on the original connection.
276     */
277    private void updateAddress() {
278        if (mOriginalConnection != null) {
279            Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
280            int presentation = mOriginalConnection.getNumberPresentation();
281            if (!Objects.equals(address, getAddress()) ||
282                    presentation != getAddressPresentation()) {
283                com.android.services.telephony.Log.v(this, "updateAddress, address changed");
284                setAddress(address, presentation);
285            }
286
287            String name = mOriginalConnection.getCnapName();
288            int namePresentation = mOriginalConnection.getCnapNamePresentation();
289            if (!Objects.equals(name, getCallerDisplayName()) ||
290                    namePresentation != getCallerDisplayNamePresentation()) {
291                com.android.services.telephony.Log
292                        .v(this, "updateAddress, caller display name changed");
293                setCallerDisplayName(name, namePresentation);
294            }
295        }
296    }
297
298    /**
299     * Determines the address for an incoming number.
300     *
301     * @param number The incoming number.
302     * @return The Uri representing the number.
303     */
304    private static Uri getAddressFromNumber(String number) {
305        // Address can be null for blocked calls.
306        if (number == null) {
307            number = "";
308        }
309        return Uri.fromParts(PhoneAccount.SCHEME_SIP, number, null);
310    }
311
312    private void close() {
313        if (getPhone() != null) {
314            getPhone().unregisterForPreciseCallStateChanged(mHandler);
315        }
316        mOriginalConnection = null;
317        destroy();
318    }
319
320    private static void log(String msg) {
321        Log.d(SipUtil.LOG_TAG, PREFIX + msg);
322    }
323}
324