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 
17 package com.android.services.telephony.sip;
18 
19 import android.net.Uri;
20 import android.os.Bundle;
21 import android.os.Handler;
22 import android.os.Message;
23 import android.telecom.AudioState;
24 import android.telecom.Connection;
25 import android.telecom.PhoneAccount;
26 import android.telecom.TelecomManager;
27 import android.util.EventLog;
28 import android.util.Log;
29 
30 import com.android.internal.telephony.Call;
31 import com.android.internal.telephony.CallStateException;
32 import com.android.internal.telephony.PhoneConstants;
33 import com.android.internal.telephony.sip.SipPhone;
34 import com.android.services.telephony.DisconnectCauseUtil;
35 
36 import java.util.Objects;
37 
38 final class SipConnection extends Connection {
39     private static final String PREFIX = "[SipConnection] ";
40     private static final boolean VERBOSE = false; /* STOP SHIP if true */
41 
42     private static final int MSG_PRECISE_CALL_STATE_CHANGED = 1;
43 
44     private final Handler mHandler = new Handler() {
45         @Override
46         public void handleMessage(Message msg) {
47             switch (msg.what) {
48                 case MSG_PRECISE_CALL_STATE_CHANGED:
49                     updateState(false);
50                     break;
51             }
52         }
53     };
54 
55     private com.android.internal.telephony.Connection mOriginalConnection;
56     private Call.State mOriginalConnectionState = Call.State.IDLE;
57 
SipConnection()58     SipConnection() {
59         if (VERBOSE) log("new SipConnection");
60         setInitializing();
61     }
62 
initialize(com.android.internal.telephony.Connection connection)63     void initialize(com.android.internal.telephony.Connection connection) {
64         if (VERBOSE) log("init SipConnection, connection: " + connection);
65         mOriginalConnection = connection;
66         if (getPhone() != null) {
67             getPhone().registerForPreciseCallStateChanged(mHandler, MSG_PRECISE_CALL_STATE_CHANGED,
68                     null);
69         }
70         updateAddress();
71         setTechnologyTypeExtra();
72         setInitialized();
73     }
74 
75     @Override
onAudioStateChanged(AudioState state)76     public void onAudioStateChanged(AudioState state) {
77         if (VERBOSE) log("onAudioStateChanged: " + state);
78         if (getPhone() != null) {
79             getPhone().setEchoSuppressionEnabled();
80         }
81     }
82 
83     @Override
onStateChanged(int state)84     public void onStateChanged(int state) {
85         if (VERBOSE) log("onStateChanged, state: " + Connection.stateToString(state));
86     }
87 
88     @Override
onPlayDtmfTone(char c)89     public void onPlayDtmfTone(char c) {
90         if (VERBOSE) log("onPlayDtmfTone");
91         if (getPhone() != null) {
92             getPhone().startDtmf(c);
93         }
94     }
95 
96     @Override
onStopDtmfTone()97     public void onStopDtmfTone() {
98         if (VERBOSE) log("onStopDtmfTone");
99         if (getPhone() != null) {
100             getPhone().stopDtmf();
101         }
102     }
103 
104     @Override
onDisconnect()105     public void onDisconnect() {
106         if (VERBOSE) log("onDisconnect");
107         try {
108             if (getCall() != null && !getCall().isMultiparty()) {
109                 getCall().hangup();
110             } else if (mOriginalConnection != null) {
111                 mOriginalConnection.hangup();
112             }
113         } catch (CallStateException e) {
114             log("onDisconnect, exception: " + e);
115         }
116     }
117 
118     @Override
onSeparate()119     public void onSeparate() {
120         if (VERBOSE) log("onSeparate");
121         try {
122             if (mOriginalConnection != null) {
123                 mOriginalConnection.separate();
124             }
125         } catch (CallStateException e) {
126             log("onSeparate, exception: " + e);
127         }
128     }
129 
130     @Override
onAbort()131     public void onAbort() {
132         if (VERBOSE) log("onAbort");
133         onDisconnect();
134     }
135 
136     @Override
onHold()137     public void onHold() {
138         if (VERBOSE) log("onHold");
139         try {
140             if (getPhone() != null && getState() == STATE_ACTIVE
141                     && getPhone().getRingingCall().getState() != Call.State.WAITING) {
142                 // Double check with the internal state since a discrepancy in states could mean
143                 // that the transactions is already in progress from a previous request.
144                 if (mOriginalConnection != null &&
145                         mOriginalConnection.getState() == Call.State.ACTIVE) {
146                     getPhone().switchHoldingAndActive();
147                 } else {
148                     log("skipping switch from onHold due to internal state:");
149                 }
150             }
151         } catch (CallStateException e) {
152             log("onHold, exception: " + e);
153         }
154     }
155 
156     @Override
onUnhold()157     public void onUnhold() {
158         if (VERBOSE) log("onUnhold");
159         try {
160             if (getPhone() != null && getState() == STATE_HOLDING &&
161                     getPhone().getForegroundCall().getState() != Call.State.DIALING) {
162                 // Double check with the internal state since a discrepancy in states could mean
163                 // that the transaction is already in progress from a previous request.
164                 if (mOriginalConnection != null &&
165                         mOriginalConnection.getState() == Call.State.HOLDING) {
166                     getPhone().switchHoldingAndActive();
167                 } else {
168                     log("skipping switch from onUnHold due to internal state.");
169                 }
170             }
171         } catch (CallStateException e) {
172             log("onUnhold, exception: " + e);
173         }
174     }
175 
176     @Override
onAnswer(int videoState)177     public void onAnswer(int videoState) {
178         if (VERBOSE) log("onAnswer");
179         try {
180             if (isValidRingingCall() && getPhone() != null) {
181                 getPhone().acceptCall(videoState);
182             }
183         } catch (CallStateException e) {
184             log("onAnswer, exception: " + e);
185         } catch (IllegalStateException e) {
186             // Call could not be answered due to an invalid audio-codec offered by the caller.  We
187             // will reject the call to stop it from ringing.
188             log("onAnswer, IllegalStateException: " + e);
189             EventLog.writeEvent(0x534e4554, "31752213", -1, "Invalid codec.");
190             onReject();
191         } catch (IllegalArgumentException e) {
192             // Call could not be answered due to an error parsing the SDP.  We will reject the call
193             // to stop it from ringing.
194             log("onAnswer, IllegalArgumentException: " + e);
195             EventLog.writeEvent(0x534e4554, "31752213", -1, "Invalid SDP.");
196             onReject();
197         }
198     }
199 
200     @Override
onReject()201     public void onReject() {
202         if (VERBOSE) log("onReject");
203         try {
204             if (isValidRingingCall() && getPhone() != null) {
205                 getPhone().rejectCall();
206             }
207         } catch (CallStateException e) {
208             log("onReject, exception: " + e);
209         }
210     }
211 
212     @Override
onPostDialContinue(boolean proceed)213     public void onPostDialContinue(boolean proceed) {
214         if (VERBOSE) log("onPostDialContinue, proceed: " + proceed);
215         // SIP doesn't have post dial support.
216     }
217 
getCall()218     private Call getCall() {
219         if (mOriginalConnection != null) {
220             return mOriginalConnection.getCall();
221         }
222         return null;
223     }
224 
getPhone()225     SipPhone getPhone() {
226         Call call = getCall();
227         if (call != null) {
228             return (SipPhone) call.getPhone();
229         }
230         return null;
231     }
232 
isValidRingingCall()233     private boolean isValidRingingCall() {
234         Call call = getCall();
235         return call != null && call.getState().isRinging() &&
236                 call.getEarliestConnection() == mOriginalConnection;
237     }
238 
updateState(boolean force)239     private void updateState(boolean force) {
240         if (mOriginalConnection == null) {
241             return;
242         }
243 
244         Call.State newState = mOriginalConnection.getState();
245         if (VERBOSE) log("updateState, " + mOriginalConnectionState + " -> " + newState);
246         if (force || mOriginalConnectionState != newState) {
247             mOriginalConnectionState = newState;
248             switch (newState) {
249                 case IDLE:
250                     break;
251                 case ACTIVE:
252                     setActive();
253                     break;
254                 case HOLDING:
255                     setOnHold();
256                     break;
257                 case DIALING:
258                 case ALERTING:
259                     setDialing();
260                     // For SIP calls, we need to ask the framework to play the ringback for us.
261                     setRingbackRequested(true);
262                     break;
263                 case INCOMING:
264                 case WAITING:
265                     setRinging();
266                     break;
267                 case DISCONNECTED:
268                     setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
269                             mOriginalConnection.getDisconnectCause()));
270                     close();
271                     break;
272                 case DISCONNECTING:
273                     break;
274             }
275             updateCallCapabilities(force);
276         }
277     }
278 
buildCallCapabilities()279     private int buildCallCapabilities() {
280         int capabilities = CAPABILITY_MUTE | CAPABILITY_SUPPORT_HOLD;
281         if (getState() == STATE_ACTIVE || getState() == STATE_HOLDING) {
282             capabilities |= CAPABILITY_HOLD;
283         }
284         return capabilities;
285     }
286 
updateCallCapabilities(boolean force)287     void updateCallCapabilities(boolean force) {
288         int newCallCapabilities = buildCallCapabilities();
289         if (force || getConnectionCapabilities() != newCallCapabilities) {
290             setConnectionCapabilities(newCallCapabilities);
291         }
292     }
293 
onAddedToCallService()294     void onAddedToCallService() {
295         if (VERBOSE) log("onAddedToCallService");
296         updateState(true);
297         updateCallCapabilities(true);
298         setAudioModeIsVoip(true);
299         if (mOriginalConnection != null) {
300             setCallerDisplayName(mOriginalConnection.getCnapName(),
301                     mOriginalConnection.getCnapNamePresentation());
302         }
303     }
304 
305     /**
306      * Updates the handle on this connection based on the original connection.
307      */
updateAddress()308     private void updateAddress() {
309         if (mOriginalConnection != null) {
310             Uri address = getAddressFromNumber(mOriginalConnection.getAddress());
311             int presentation = mOriginalConnection.getNumberPresentation();
312             if (!Objects.equals(address, getAddress()) ||
313                     presentation != getAddressPresentation()) {
314                 com.android.services.telephony.Log.v(this, "updateAddress, address changed");
315                 setAddress(address, presentation);
316             }
317 
318             String name = mOriginalConnection.getCnapName();
319             int namePresentation = mOriginalConnection.getCnapNamePresentation();
320             if (!Objects.equals(name, getCallerDisplayName()) ||
321                     namePresentation != getCallerDisplayNamePresentation()) {
322                 com.android.services.telephony.Log
323                         .v(this, "updateAddress, caller display name changed");
324                 setCallerDisplayName(name, namePresentation);
325             }
326         }
327     }
328 
setTechnologyTypeExtra()329     private void setTechnologyTypeExtra() {
330         int phoneType = PhoneConstants.PHONE_TYPE_SIP;
331         if (getExtras() == null) {
332             Bundle b = new Bundle();
333             b.putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, phoneType);
334             setExtras(b);
335         } else {
336             getExtras().putInt(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE, phoneType);
337         }
338     }
339 
340     /**
341      * Determines the address for an incoming number.
342      *
343      * @param number The incoming number.
344      * @return The Uri representing the number.
345      */
getAddressFromNumber(String number)346     private static Uri getAddressFromNumber(String number) {
347         // Address can be null for blocked calls.
348         if (number == null) {
349             number = "";
350         }
351         return Uri.fromParts(PhoneAccount.SCHEME_SIP, number, null);
352     }
353 
close()354     private void close() {
355         if (getPhone() != null) {
356             getPhone().unregisterForPreciseCallStateChanged(mHandler);
357         }
358         mOriginalConnection = null;
359         destroy();
360     }
361 
log(String msg)362     private static void log(String msg) {
363         Log.d(SipUtil.LOG_TAG, PREFIX + msg);
364     }
365 }
366