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