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