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