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